def test_from_string(self): try: time = FirstTime.from_string(string='2:03:23') self.assertEqual('2:03:23', str(time)) except ValueError: self.fail('test_from_string should not fail for valid string') try: _ = FirstTime.from_string(string='abc') self.fail('FirstTime is expected to fail with "abc"') except ValueError as ex: self.assertEqual('(\'Unknown string format:\', \'abc\')', str(ex)) try: time = FirstTime.from_string(string='3:45') self.assertEqual('3:45:00', str(time)) except ValueError: self.fail('test_from_string should not fail for valid string') try: _ = FirstTime.from_string(string='3') self.fail('test_from_string should not pass for "3"') except ValueError as ex: self.assertEqual('unknown string format for "3"', str(ex)) try: time = FirstTime.from_string(string='4/15/2015') self.assertEqual( 'test_from_string should not pass for "4/15/2015"', time) except ValueError as ex: self.assertEqual('unknown string format for "4/15/2015"', str(ex))
def test_add_workout(self): ws1 = [0, 2, 5] rt1 = FirstRaceType(name='Marathon', distance=FirstDistance.from_string('42.195 km')) rd1 = date(year=2017, month=7, day=29) r1 = FirstRace(name='SFM', race_type=rt1, race_date=rd1) rn1 = FirstRunner(name='DBD') p1 = FirstPlan(name='My first marathon training plan', weekly_schedule=ws1, race=r1, runner=rn1) t_warmup = FirstTime.from_string('0:15:00') p_warmup = FirstPace.from_string('0:10:00 min per mile') s_warmup = FirstStepBody(name='Warm up', pace=p_warmup, time=t_warmup) s_repeat = FirstStepRepeat(name='repeat X 8', repeat=8) d_interval = FirstDistance.from_string('400 m') p_fast = FirstPace.from_string('0:08:00 min per mile') s_fast = FirstStepBody(name='Fast', pace=p_fast, distance=d_interval) s_repeat.add_step(s_fast) s_slow = FirstStepBody(name='Rest', pace=p_warmup, distance=d_interval) s_repeat.add_step(s_slow) t_cooldown = FirstTime.from_string('0:10:00') s_cooldown = FirstStepBody(name='Cool down', pace=p_warmup, time=t_cooldown) wo = FirstWorkout(name='Week 1 Key-run 1', workout_date=date(year=2017, month=6, day=24)) wo.add_step(step=s_warmup) wo.add_step(step=s_repeat) wo.add_step(step=s_cooldown) try: # first workout p1.add_workout(workout=wo) cmp_string = ('Training Plan:\nName - "My first marathon training plan"\n' + 'Workout days: Mon, Wed, Sat\nRace:\n' + ' Name - "SFM" of type Marathon - 42.195 km\nRunner:\n Name - "DBD"\nWorkouts:\n' + ' "Week 1 Key-run 1"\n Sat 2017-06-24\n scheduled\n' + 'Total 1 workouts\n') self.assertEqual(cmp_string, str(p1)) file_name = 'cmp_plan2.tcx' with open('{}/{}'.format(Config.TEST_RESOURCE_DIR, file_name), 'r') as from_file: cmp_string = from_file.read() self.assertEqual(cmp_string, p1.tcx()) file_name = 'cmp_plan2.json' with open('{}/{}'.format(Config.TEST_RESOURCE_DIR, file_name), 'r') as from_file: cmp_json = json.load(from_file) self.assertEqual(cmp_json, p1.to_json()) file_name = 'cmp_plan2_km.json' with open('{}/{}'.format(Config.TEST_RESOURCE_DIR, file_name), 'r') as from_file: cmp_json = json.load(from_file) self.assertEqual(cmp_json, p1.to_json(output_unit='km')) file_name = 'cmp_plan2.html' with open('{}/{}'.format(Config.TEST_RESOURCE_DIR, file_name), 'r') as from_file: cmp_html = from_file.read() self.assertEqual(cmp_html, p1.to_html()) file_name = 'cmp_plan2_km.html' with open('{}/{}'.format(Config.TEST_RESOURCE_DIR, file_name), 'r') as from_file: cmp_html = from_file.read() self.assertEqual(cmp_html, p1.to_html(output_unit='km')) except TypeError as ex: self.fail(str(ex))
def test_from_string(self): try: t1 = FirstTime.from_string('2:03:23') self.assertEqual('2:03:23', str(t1)) except ValueError: self.fail('test_from_string hould not fail for valid string') try: t2 = FirstTime.from_string('abc') self.fail('FirstTime is expected to fail with "abc"') except ValueError as ex: self.assertEqual('FirstTime.from_string - unknown string format - "abc"', str(ex)) try: t3 = FirstTime.from_string('3:45') self.assertEqual('3:45:00', str(t3)) except ValueError: self.fail('test_from_string should not fail for valid string') try: t4 = FirstTime.from_string('3') self.fail('test_from_string should not pass for "3"') except ValueError as ex: self.assertEqual('FirstTime.from_string - unknown string format for "3"', str(ex)) try: t5 = FirstTime.from_string('4/15/2015') self.assertEqual('test_from_string should not pass for "4/15/2015"') except ValueError as ex: self.assertEqual('FirstTime.from_string - unknown string format for "4/15/2015"', str(ex))
def __get_segments_json(self, json_segments: Dict) -> None: self.reference_race = json_segments['reference_race'] index = 0 for segment_type in json_segments['segment_types']: name = segment_type['name'] type_str = segment_type['type'] ref_pace = segment_type['ref_pace_name'] if type_str == 'DISTANCE': distance = FirstDistance.from_string( string=segment_type['distance']) self.segments.append(FirstSegment(name=name, distance=distance)) elif type_str == 'TIME': duration = FirstTime.from_string(string=segment_type['time']) self.segments.append( FirstSegment(name=name, duration=duration, ref_pace_name=ref_pace)) else: # PACE self.segments.append( FirstSegment(name=name, ref_pace_name=ref_pace)) self.segments_lookup[name] = index index += 1 pace_unit = json_segments['pace_unit'] distance_unit = pace_unit.split()[-1] for line in json_segments['paces']: items = line.split() paces_list = [FirstTime.from_string(items[0])] index = 0 for pace_str in items[1:]: time_str = '0:{}'.format(pace_str) ref_segment = self.segments[index] if ref_segment.get_type() == 'distance': cur_time = FirstTime.from_string(time_str) cur_distance = ref_segment.distance paces_list.append( FirstPace.from_time_distance(time=cur_time, distance=cur_distance, unit=distance_unit)) elif ref_segment.get_type() == 'pace': paces_list.append( FirstPace.from_string('{} {}'.format( time_str, pace_unit))) else: raise ValueError( 'Duration segments have already a reference pace') index += 1 self.segments_paces.append(paces_list)
def main(): args = process_args() if args.output not in ['text', 'tcx', 'both']: raise ValueError('output should be one of "text", "tcx", or "both"') data_file_path = expanduser('~') + '/PycharmProjects/first/database/FIRSTregularPlans.xml' data = FirstData(xml_path=data_file_path) runner = FirstRunner(name=args.runner_name) target_time = FirstTime.from_string(args.target_time) if args.ref_race_type is not None: target_time = data.equivalent_time(time_from=target_time, race_index_from=data.race_type_index_by_name(args.ref_race_type), race_index_to=data.race_type_index_by_name(args.race_type)) if args.race_name is not None: race_name = args.race_name else: race_name = args.race_type race_date = datetime.datetime.strptime(args.race_date, '%m/%d/%Y').date() race = FirstRace(race_type=data.get_race_type_by_name(args.race_type), name=race_name, race_date=race_date, target_time=target_time) ws = get_keyrun_days(args.keyrun_days) plan = FirstPlan(name=args.race_name, weekly_schedule=ws, race=race, runner=runner) plan.generate_workouts(data=data) base_file_name = str(race_date) + race_name if args.output == 'text' or args.output == 'both': file_name = expanduser('~/Downloads/') + base_file_name + '.txt' target = open(file_name, 'w') target.write(plan.details(level=3)) target.close() if args.output == 'tcx' or args.output == 'both': file_name = expanduser('~/Downloads/') + base_file_name + '.tcx' target = open(file_name, 'w') target.write(plan.tcx()) target.close()
def __init__(self, json_path: str): """ Constructor :return: instance of FirstData :rtype: FirstData """ self.race_types = [] self.race_times = [] self.segments = [] self.segments_lookup = {} self.reference_race = None self.segments_paces = [] self.plan_instructions = [] if json_path is not None: with open(json_path, 'r') as fd: data_dict = json.load(fd) self.name = data_dict['name'] self.note = data_dict['note'] self.race_types = [ FirstRaceType(name=race['name'], distance=FirstDistance.from_string( race['distance'])) for race in data_dict['races'] ] self.race_times = [[ FirstTime.from_string(string=time) for time in times ] for times in data_dict['equivalent_times']] self.__get_segments_json(json_segments=data_dict['segments']) self.plan_instructions = [ PlanInstructions( name=plan['name'], race_name=plan['race_name'], instructions=[line for line in plan['instructions']]) for plan in data_dict['workout_instructions'] ] else: raise ValueError('json_path should point to an existing file')
def from_string(cls, str_input): """ Constructor: Instantiate FirstPace from a string input :param str_input: format - '0:MM:SS per unit' :type str_input: str :return: instance of FirstPace :rtype: FirstPace """ where_am_i = 'FirstPace.from_string' tokens = str_input.split() p_time = FirstTime.from_string(tokens[0]) # pass the exception on length_unit = tokens[-1] if not FirstDistance.is_valid_unit(unit=tokens[-1]): raise ValueError(where_am_i + ' - "%1s" is not a valid length unit' % length_unit) return cls(minutes=p_time.seconds//60, seconds=p_time.seconds % 60, length_unit=length_unit)
def __parse_segments(self, segment_def, ref_race, pace_unit, segment_paces): where_am_i = 'FirstData.__parse_segments' index = 0 for segment in segment_def: name = segment[0].text segment_type = segment[1].text distance = None duration = None ref_pace_name = None if segment_type == 'DISTANCE': distance = FirstDistance(distance=float(segment[2][0].text), unit=segment[2][1].text) elif segment_type == 'TIME': duration = FirstTime(seconds=int(segment[2][0].text), minutes=int(segment[2][1].text), hours=int(segment[2][2].text)) ref_pace_name = segment[3].text else: # PACE ref_pace_name = segment[2].text self.segments.append( FirstSegment(name=name, distance=distance, duration=duration, ref_pace_name=ref_pace_name)) self.segments_lookup[name] = index index += 1 self.reference_race = ref_race.text dist_unit = pace_unit.text.split()[-1] for line in segment_paces: paces_list = [] first = True index = 0 for value in line.text.split(): if first: paces_list.append(FirstTime.from_string(value)) first = False else: # why incrementing index makes ref_segment.get_type undefined? time_string = '0:' + value ref_segment = self.segments[index] if ref_segment.get_type() == 'distance': cur_time = FirstTime.from_string(time_string) cur_dist = ref_segment.distance paces_list.append( FirstPace.from_time_distance(time=cur_time, distance=cur_dist, unit=dist_unit)) elif ref_segment.get_type() == 'pace': paces_list.append( FirstPace.from_string(time_string + ' ' + pace_unit.text)) else: raise ValueError( where_am_i + ' - Duration segments have already a reference pace' ) index = index + 1 self.segments_paces.append(paces_list)
def test_repeat(self): FirstStepBase.reset_global_id() pace = FirstPace.from_string(str_input='0:10:00 min per mile') distance = FirstDistance.from_string(string='3 mile') time = FirstTime.from_string(string='0:15:00') try: name = '3 X (3 mile @ 10 min per mile + 15 minutes @ 19 min per mile)' step_r = FirstStepRepeat(name=name, repeat=3) self.assertEqual( 'Step: "' + name + '" id = 0\ntype - repeat repeat - 3\n', str(step_r)) self.assertAlmostEqual(0.0, step_r.total(unit='mile'), 5) self.assertAlmostEqual(0.0, step_r.total(what='time', unit='minute')) tcx_string = ( '<Step xsi:type="Repeat_t">\n' + ' <StepId>0</StepId>\n' + ' <Name>3 X (3 mile @ 10 min per mile + 15 minutes @ 19 min per mile)</Name>\n' + ' <Repetitions>3</Repetitions>\n' + '</Step>') self.assertEqual(tcx_string, step_r.tcx().indented_str()) except TypeError as tex: self.fail(str(tex)) except ValueError as vex: self.fail(str(vex)) try: step_b1 = FirstStepBody(name='3 mile @ 10 min per mile', pace=pace, distance=distance) step_b2 = FirstStepBody(name='15 minutes @ 19 min per mile', pace=pace, time=time) step_r.add_step(step_b1) step_r.add_step(step_b2) short = 'Step: "' + name + '" id = 0\ntype - repeat repeat - 3\n' self.assertEqual(short, str(step_r)) detail = 'Step: "3 X (3 mile @ 10 min per mile + 15 minutes @ 19 min per mile)"\n' + \ ' Step: "3 mile @ 10 min per mile"\n' + \ ' 3.0 mile at 0:10:00 min per mile\n' + \ ' Step: "15 minutes @ 19 min per mile"\n' + \ ' 0:15:00 at 0:10:00 min per mile\n' self.assertEqual(detail, step_r.details()) self.assertAlmostEqual(13.5, step_r.total(unit='mile'), 5) self.assertAlmostEqual(135.0, step_r.total(what='time', unit='minute')) tcx_string = ( '<Step xsi:type="Repeat_t">\n' + ' <StepId>0</StepId>\n' + ' <Name>3 X (3 mile @ 10 min per mile + 15 minutes @ 19 min per mile)</Name>\n' + ' <Repetitions>3</Repetitions>\n' + ' <Child xsi:type="Step_t">\n' + ' <StepId>1</StepId>\n' + ' <Name>3 mile @ 10 min per mile</Name>\n' + ' <Duration xsi:type="Distance_t">\n' + ' <Meters>4828</Meters>\n' + ' </Duration>\n' + ' <Intensity>Active</Intensity>\n' + ' <Target xsi:type="Speed_t">\n' + ' <SpeedZone xsi:type="CustomSpeedZone_t">\n' + ' <LowInMetersPerSecond>2.6382689</LowInMetersPerSecond>\n' + ' <HighInMetersPerSecond>2.7277017</HighInMetersPerSecond>\n' + ' </SpeedZone>\n' + ' </Target>\n' + ' </Child>\n' + ' <Child xsi:type="Step_t">\n' + ' <StepId>2</StepId>\n' + ' <Name>15 minutes @ 19 min per mile</Name>\n' + ' <Duration xsi:type="Time_t">\n' + ' <Seconds>900</Seconds>\n' + ' </Duration>\n' + ' <Intensity>Active</Intensity>\n' + ' <Target xsi:type="Speed_t">\n' + ' <SpeedZone xsi:type="CustomSpeedZone_t">\n' + ' <LowInMetersPerSecond>2.6382689</LowInMetersPerSecond>\n' + ' <HighInMetersPerSecond>2.7277017</HighInMetersPerSecond>\n' + ' </SpeedZone>\n' + ' </Target>\n' + ' </Child>\n' '</Step>') self.assertEqual(tcx_string, step_r.tcx(delta_seconds=10).indented_str()) cmp_json = { 'name': '3 X (3 mile @ 10 min per mile + 15 minutes @ 19 min per mile)', 'repeat': 3, 'steps': [{ 'distance': { 'distance': 3.0, 'unit': 'mile' }, 'name': '3 mile @ 10 min per mile', 'pace': { 'length_unit': 'mile', 'pace': '0:10:00 min per mile', 'time': { 'seconds': 600, 'time': '0:10:00' } } }, { 'name': '15 minutes @ 19 min per mile', 'pace': { 'length_unit': 'mile', 'pace': '0:10:00 min per mile', 'time': { 'seconds': 600, 'time': '0:10:00' } }, 'time': { 'seconds': 900, 'time': '0:15:00' } }] } self.assertEqual(cmp_json, step_r.to_json()) cmp_json = { 'name': '3 X (3 mile @ 10 min per mile + 15 minutes @ 19 min per mile)', 'repeat': 3, 'steps': [{ 'distance': { 'distance': 4.828032, 'unit': 'km' }, 'name': '3 mile @ 10 min per mile', 'pace': { 'length_unit': 'km', 'pace': '0:06:13 min per km', 'time': { 'seconds': 373, 'time': '0:06:13' } } }, { 'name': '15 minutes @ 19 min per mile', 'pace': { 'length_unit': 'km', 'pace': '0:06:13 min per km', 'time': { 'seconds': 373, 'time': '0:06:13' } }, 'time': { 'seconds': 900, 'time': '0:15:00' } }] } self.assertEqual(cmp_json, step_r.to_json(output_unit='km')) cmp_html = ( '<div style="margin-left: 20px">\n' + ' <p>\n' + ' Repeat 3 times:\n' + ' </p>\n' + ' <div style="margin-left: 20px">\n' + ' <p>\n' + ' 3 mile @ 10 min per mile - 3.000 mile at 0:10:00 min per mile\n' + ' </p>\n' + ' </div>\n' + ' <div style="margin-left: 20px">\n' + ' <p>\n' + ' 15 minutes @ 19 min per mile - 0:15:00 at 0:10:00 min per mile\n' + ' </p>\n' + ' </div>\n' + '</div>') self.assertEqual(cmp_html, step_r.to_html().indented_str()) cmp_html = ( '<div style="margin-left: 20px">\n' + ' <p>\n' + ' Repeat 3 times:\n' + ' </p>\n' + ' <div style="margin-left: 20px">\n' + ' <p>\n' + ' 3 mile @ 10 min per mile - 4.828 km at 0:06:13 min per km\n' + ' </p>\n' + ' </div>\n' + ' <div style="margin-left: 20px">\n' + ' <p>\n' + ' 15 minutes @ 19 min per mile - 0:15:00 at 0:06:13 min per km\n' + ' </p>\n' + ' </div>\n' + '</div>') self.assertEqual(cmp_html, step_r.to_html(output_unit='km').indented_str()) except TypeError as tex: self.fail(str(tex)) except ValueError as vex: self.fail(str(vex)) try: # negative repeat _ = FirstStepRepeat(name='bla', repeat=-3) self.fail('Should not get here with negative repeat value') except ValueError as ex: self.assertEqual('repeat must be greater than 0', str(ex))
def test_body(self): FirstStepBase.reset_global_id() pace = FirstPace.from_string(str_input='0:10:00 min per mile') distance = FirstDistance.from_string(string='3 mile') time = FirstTime.from_string(string='0:15:00') try: # distance step step_b = FirstStepBody(name='3 miles @ 10 minutes per mile', pace=pace, distance=distance) cmp_string = 'Step: "3 miles @ 10 minutes per mile" id = 0\n' + \ 'type - body pace - 0:10:00 min per mile\nDistance - 3.0 mile\n' self.assertEqual(cmp_string, str(step_b)) self.assertAlmostEqual(3.0, step_b.total(unit='mile'), 5) self.assertAlmostEqual(4.828032, step_b.total(unit='km'), 5) self.assertAlmostEqual(30.0, step_b.total(what='time', unit='minute'), 5) self.assertAlmostEqual(0.5, step_b.total(what='time', unit='hour'), 5) tcx_string = ( '<Step xsi:type="Step_t">\n' + ' <StepId>0</StepId>\n' + ' <Name>3 miles @ 10 minutes per mile</Name>\n' + ' <Duration xsi:type="Distance_t">\n' + ' <Meters>4828</Meters>\n' + ' </Duration>\n' + ' <Intensity>Active</Intensity>\n' + ' <Target xsi:type="Speed_t">\n' + ' <SpeedZone xsi:type="CustomSpeedZone_t">\n' + ' <LowInMetersPerSecond>2.6600727</LowInMetersPerSecond>\n' + ' <HighInMetersPerSecond>2.7047798</HighInMetersPerSecond>\n' + ' </SpeedZone>\n' + ' </Target>\n' + '</Step>') self.assertEqual(tcx_string, step_b.tcx().indented_str()) # no indent tcx_string = ( '<Step xsi:type="Step_t">\n' + ' <StepId>0</StepId>\n' + ' <Name>3 miles @ 10 minutes per mile</Name>\n' + ' <Duration xsi:type="Distance_t">\n' + ' <Meters>4828</Meters>\n' + ' </Duration>\n' + ' <Intensity>Active</Intensity>\n' + ' <Target xsi:type="Speed_t">\n' + ' <SpeedZone xsi:type="CustomSpeedZone_t">\n' + ' <LowInMetersPerSecond>2.6688955</LowInMetersPerSecond>\n' + ' <HighInMetersPerSecond>2.6957186</HighInMetersPerSecond>\n' + ' </SpeedZone>\n' + ' </Target>\n' + '</Step>') self.assertEqual(tcx_string, step_b.tcx(delta_seconds=3).indented_str()) cmp_json = { 'distance': { 'distance': 3.0, 'unit': 'mile' }, 'name': '3 miles @ 10 minutes per mile', 'pace': { 'length_unit': 'mile', 'pace': '0:10:00 min per mile', 'time': { 'seconds': 600, 'time': '0:10:00' } } } self.assertEqual(cmp_json, step_b.to_json()) cmp_json = { 'distance': { 'distance': 4.828032, 'unit': 'km' }, 'name': '3 miles @ 10 minutes per mile', 'pace': { 'length_unit': 'km', 'pace': '0:06:13 min per km', 'time': { 'seconds': 373, 'time': '0:06:13' } } } self.assertEqual(cmp_json, step_b.to_json(output_unit='km')) cmp_html = ( '<div style="margin-left: 20px">\n' + ' <p>\n' + ' 3 miles @ 10 minutes per mile - 3.000 mile at 0:10:00 min per mile\n' + ' </p>\n' + '</div>') self.assertEqual(cmp_html, step_b.to_html().indented_str()) cmp_html = ( '<div style="margin-left: 20px">\n' + ' <p>\n' + ' 3 miles @ 10 minutes per mile - 4.828 km at 0:06:13 min per km\n' + ' </p>\n' + '</div>') self.assertEqual(cmp_html, step_b.to_html(output_unit='km').indented_str()) except TypeError as tex: self.fail(str(tex)) except ValueError as vex: self.fail(str(vex)) try: # time step step_b = FirstStepBody(name='15 minutes @ 10 minutes per mile', pace=pace, time=time) cmp_string = 'Step: "15 minutes @ 10 minutes per mile" id = 1\n' + \ 'type - body pace - 0:10:00 min per mile\nTime - 0:15:00\n' self.assertEqual(cmp_string, str(step_b)) self.assertAlmostEqual(15.0, step_b.total(what='time', unit='minute'), 5) self.assertAlmostEqual(7920.0, step_b.total(unit='ft')) tcx_string = ( '<Step xsi:type="Step_t">\n' + ' <StepId>1</StepId>\n' + ' <Name>15 minutes @ 10 minutes per mile</Name>\n' + ' <Duration xsi:type="Time_t">\n' + ' <Seconds>900</Seconds>\n' + ' </Duration>\n' + ' <Intensity>Active</Intensity>\n' + ' <Target xsi:type="Speed_t">\n' + ' <SpeedZone xsi:type="CustomSpeedZone_t">\n' + ' <LowInMetersPerSecond>2.6600727</LowInMetersPerSecond>\n' + ' <HighInMetersPerSecond>2.7047798</HighInMetersPerSecond>\n' + ' </SpeedZone>\n' + ' </Target>\n' + '</Step>') self.assertEqual(tcx_string, step_b.tcx().indented_str()) cmp_json = { 'name': '15 minutes @ 10 minutes per mile', 'pace': { 'length_unit': 'mile', 'pace': '0:10:00 min per mile', 'time': { 'seconds': 600, 'time': '0:10:00' } }, 'time': { 'seconds': 900, 'time': '0:15:00' } } self.assertEqual(cmp_json, step_b.to_json()) cmp_json = { 'name': '15 minutes @ 10 minutes per mile', 'pace': { 'length_unit': 'km', 'pace': '0:06:13 min per km', 'time': { 'seconds': 373, 'time': '0:06:13' } }, 'time': { 'seconds': 900, 'time': '0:15:00' } } self.assertEqual(cmp_json, step_b.to_json(output_unit='km')) cmp_html = ( '<div style="margin-left: 20px">\n' + ' <p>\n' + ' 15 minutes @ 10 minutes per mile - 0:15:00 at 0:10:00 min per mile\n' + ' </p>\n' + '</div>') self.assertEqual(cmp_html, step_b.to_html().indented_str()) cmp_html = ( '<div style="margin-left: 20px">\n' + ' <p>\n' + ' 15 minutes @ 10 minutes per mile - 0:15:00 at 0:06:13 min per km\n' + ' </p>\n' + '</div>') self.assertEqual(cmp_html, step_b.to_html(output_unit='km').indented_str()) except TypeError as tex: self.fail(str(tex)) except ValueError as vex: self.fail(str(vex)) try: # no distance and no time _ = FirstStepBody(name='dummy', pace=pace) self.fail('Should not get here with neither distance nor duration') except ValueError as ex: self.assertEqual('Either distance or time must have a value', str(ex)) try: # both distance and time _ = FirstStepBody(name='dummy', pace=pace, distance=distance, time=time) self.fail('Should not get here with both distance and time') except ValueError as ex: self.assertEqual( 'Cannot set both distance and duration in the same step', str(ex))
def test_equivalent_time(self): data_file_path = expanduser( '~') + '/PycharmProjects/first/database/FIRSTregularPlans.xml' try: # good path data = FirstData(xml_path=data_file_path) self.assertEqual(4, len(data.race_types)) self.assertEqual(1, data.race_type_index_by_name('10K')) self.assertEqual(2, data.race_type_index_by_name('HalfMarathon')) self.assertEqual(91, len(data.race_times)) self.assertEqual(4, len(data.race_times[0])) from_time = FirstTime.from_string('0:20:13') self.assertEqual( '1:34:15', str( data.equivalent_time(time_from=from_time, race_index_from=0, race_index_to=2))) from_time = FirstTime.from_string('1:54:32') self.assertEqual( '4:01:39', str( data.equivalent_time(time_from=from_time, race_index_from=2, race_index_to=3))) except ValueError as vex: self.fail(str(vex)) except IOError as ioex: self.fail(str(ioex)) try: # bad race name index = data.race_type_index_by_name('lulu') self.fail('Should not get here with a bad race type name') except ValueError as ex: self.assertEqual( 'FirstSegment.race_type_index_by_name - Race type lulu not found', str(ex)) try: # time not found high from_time = FirstTime.from_string('4:49:59') equivalent_time = data.equivalent_time(time_from=from_time, race_index_from=2, race_index_to=0) self.fail('Should not get here with time not found') except ValueError as ex: self.assertEqual( 'FirstData.equivalent_time - time is longer than the highest database time', str(ex)) try: # time not found low from_time = FirstTime.from_string('0:49:59') equivalent_time = data.equivalent_time(time_from=from_time, race_index_from=2, race_index_to=0) self.fail('Should not get here with time not found') except ValueError as ex: self.assertEqual( 'FirstData.equivalent_time - time is shorter than the lowest database time', str(ex)) try: # bad time type equivalent_time = data.equivalent_time(time_from='abc', race_index_from=0, race_index_to=1) self.fail('Should not get here with bad time type') except TypeError as ex: self.assertEqual( 'FirstData.equivalent_time - time_from must be an instance of FirstTime', str(ex)) try: # bad index type equivalent_time = data.equivalent_time(time_from=from_time, race_index_from='abc', race_index_to=2) self.fail('Should not get here with bad index') except TypeError as ex: self.assertEqual( 'FirstData.equivalent_time - race_index_from must be an int', str(ex)) try: # bad index type equivalent_time = data.equivalent_time(time_from=from_time, race_index_from=0, race_index_to='abc') self.fail('Should not get here with bad index') except TypeError as ex: self.assertEqual( 'FirstData.equivalent_time - race_index_to must be an int', str(ex)) try: # index out of range equivalent_time = data.equivalent_time(time_from=from_time, race_index_from=-1, race_index_to=2) self.fail('Should not get here with bad index') except ValueError as ex: self.assertEqual( 'FirstData.equivalent_time - race index must be between 0 and 3', str(ex)) try: # index out of range equivalent_time = data.equivalent_time(time_from=from_time, race_index_from=4, race_index_to=2) self.fail('Should not get here with bad index') except ValueError as ex: self.assertEqual( 'FirstData.equivalent_time - race index must be between 0 and 3', str(ex)) try: # index out of range equivalent_time = data.equivalent_time(time_from=from_time, race_index_from=1, race_index_to=-2) self.fail('Should not get here with bad index') except ValueError as ex: self.assertEqual( 'FirstData.equivalent_time - race index must be between 0 and 3', str(ex)) try: # index out of range equivalent_time = data.equivalent_time(time_from=from_time, race_index_from=1, race_index_to=55) self.fail('Should not get here with bad index') except ValueError as ex: self.assertEqual( 'FirstData.equivalent_time - race index must be between 0 and 3', str(ex)) try: # bad database file bad_data = FirstData(xml_path='lulu') self.fail('Should not get here with bad file name') except IOError as ioex: self.assertEqual("[Errno 2] No such file or directory: 'lulu'", str(ioex))
def test_repeat(self): FirstStepBase.reset_global_id() p1 = FirstPace.from_string('0:10:00 min per mile') d1 = FirstDistance.from_string('3 mile') t1 = FirstTime.from_string('0:15:00') try: name = '3 X (3 mile @ 10 min per mile + 15 minutes @ 19 min per mile)' s1 = FirstStepRepeat(name=name, repeat=3) self.assertEqual( 'Step: "' + name + '" id = 0\ntype - repeat repeat - 3\n', str(s1)) self.assertAlmostEquals(0.0, s1.total(unit='mile'), 5) self.assertAlmostEquals(0.0, s1.total(what='time', unit='minute')) tcx_string = ( '<Step xsi:type="Repeat_t">\n' + ' <StepId>0</StepId>\n' + ' <Name>3 X (3 mile @ 10 min per mile + 15 minutes @ 19 min per mile)</Name>\n' + ' <Repetitions>3</Repetitions>\n' + '</Step>\n') self.assertEqual(tcx_string, s1.tcx()) except TypeError as tex: self.fail(str(tex)) except ValueError as vex: self.fail(str(vex)) try: s2 = FirstStepBody(name='3 mile @ 10 min per mile', pace=p1, distance=d1) s3 = FirstStepBody(name='15 minutes @ 19 min per mile', pace=p1, time=t1) s1.add_step(s2) s1.add_step(s3) short = 'Step: "' + name + '" id = 0\ntype - repeat repeat - 3\n' self.assertEqual(short, str(s1)) detail = 'Step: "3 X (3 mile @ 10 min per mile + 15 minutes @ 19 min per mile)"\n' +\ ' Step: "3 mile @ 10 min per mile"\n' +\ ' 3.0 mile at 0:10:00 min per mile\n' +\ ' Step: "15 minutes @ 19 min per mile"\n' +\ ' 0:15:00 at 0:10:00 min per mile\n' self.assertEqual(detail, s1.details()) self.assertAlmostEquals(13.5, s1.total(unit='mile'), 5) self.assertAlmostEquals(135.0, s1.total(what='time', unit='minute')) tcx_string = ( ' <Step xsi:type="Repeat_t">\n' + ' <StepId>0</StepId>\n' + ' <Name>3 X (3 mile @ 10 min per mile + 15 minutes @ 19 min per mile)</Name>\n' + ' <Repetitions>3</Repetitions>\n' + ' <Child xsi:type="Step_t">\n' + ' <StepId>1</StepId>\n' + ' <Name>3 mile @ 10 min per mile</Name>\n' + ' <Duration xsi:type="Distance_t">\n' + ' <Meters>4828</Meters>\n' + ' </Duration>\n' + ' <Intensity>Active</Intensity>\n' + ' <Target xsi:type="Speed_t">\n' + ' <SpeedZone xsi:type="CustomSpeedZone_t">\n' + ' <LowInMetersPerSecond>2.6382689</LowInMetersPerSecond>\n' + ' <HighInMetersPerSecond>2.7277017</HighInMetersPerSecond>\n' + ' </SpeedZone>\n' + ' </Target>\n' + ' </Child>\n' + ' <Child xsi:type="Step_t">\n' + ' <StepId>2</StepId>\n' + ' <Name>15 minutes @ 19 min per mile</Name>\n' + ' <Duration xsi:type="Time_t">\n' + ' <Seconds>900</Seconds>\n' + ' </Duration>\n' + ' <Intensity>Active</Intensity>\n' + ' <Target xsi:type="Speed_t">\n' + ' <SpeedZone xsi:type="CustomSpeedZone_t">\n' + ' <LowInMetersPerSecond>2.6382689</LowInMetersPerSecond>\n' + ' <HighInMetersPerSecond>2.7277017</HighInMetersPerSecond>\n' + ' </SpeedZone>\n' + ' </Target>\n' + ' </Child>\n' ' </Step>\n') self.assertEqual(tcx_string, s1.tcx(indent=' ', delta_seconds=10)) except TypeError as tex: self.fail(str(tex)) except ValueError as vex: self.fail(str(vex)) try: # bad repeat type dummy = FirstStepRepeat(name='bla', repeat='3') self.fail('Should not get here with bad repeat type') except TypeError as ex: self.assertEqual( 'FirstStepRepeat.__init__ - repeat must be an integer', str(ex)) try: # negative repeat dummy = FirstStepRepeat(name='bla', repeat=-3) self.fail('Should not get here with negative repeat value') except ValueError as ex: self.assertEqual( 'FirstStepRepeat.__init__ - repeat must be greater than 0', str(ex)) try: # bad child step type s1.add_step('bad step') self.fail('Should not get here with bad step type') except TypeError as ex: self.assertEqual( 'FirstStepRepeat.add_step - step must be an instance of FirstStepBase', str(ex))
def test_to_string(self): rt1 = FirstRaceType(name='5K', distance=5.0, unit='km') rd1 = date(year=2017, month=7, day=29) tt1 = FirstTime.from_string('0:25:30') tt2 = FirstTime.from_string('0:24:34') try: # positive r1 = FirstRace(race_type=rt1, race_date=rd1, name='Martial Cottle Park 5K', target_time=tt1) cmp_string = ('Martial Cottle Park 5K of type ' + str(rt1) + '\n' + 'On ' + str(rd1) + '\n' + 'Target time - ' + str(tt1) + '\n' + 'Status - scheduled\n') self.assertEqual(cmp_string, str(r1)) except TypeError as tex: self.fail(str(tex)) except ValueError as vex: self.fail(str(vex)) try: # add actual time r1.set_status('done') r1.set_actual_time(tt2) cmp_string = ('Martial Cottle Park 5K of type ' + str(rt1) + '\n' + 'On ' + str(rd1) + '\n' + 'Target time - ' + str(tt1) + '\n' + 'Status - done\n' + 'Actual time - ' + str(tt2) + '\n') self.assertEqual(cmp_string, str(r1)) except TypeError as tex: self.fail(str(tex)) except ValueError as vex: self.fail(str(vex)) try: # remove target time r1.set_target_time() cmp_string = ('Martial Cottle Park 5K of type ' + str(rt1) + '\n' + 'On ' + str(rd1) + '\n' + 'Status - done\n' + 'Actual time - ' + str(tt2) + '\n') self.assertEqual(cmp_string, str(r1)) except TypeError as tex: self.fail(str(tex)) except ValueError as vex: self.fail(str(vex)) try: # negative r1.set_target_time(1243) self.fail('Should not get here with bad target time') except TypeError as ex: self.assertEqual('FirstRace.set_target_time - a_time must be an instance of FirstTime', str(ex)) try: # negative r1.set_actual_time(1243) self.fail('Should not get here with bad actual time') except TypeError as ex: self.assertEqual('FirstRace.set_actual_time - a_time must be an instance of FirstTime', str(ex)) try: # negative r1.set_status(1234) self.fail('Should not get here with bad status') except TypeError as ex: self.assertEqual('FirstRace.set_status - status must be a string', str(ex)) try: # negative r1.set_status('lulu') self.fail('Should not get here with a bad status') except ValueError as ex: self.assertEqual("FirstRace.set_status - Status not in ['scheduled', 'done', 'skipped']", str(ex))
def main(): args = process_args() if 'text' not in args.output and 'tcx' not in args.output and \ 'json' not in args.output and 'html' not in args.output: raise ValueError( 'Unknown output formats (). Available text, tcx, html, and json'. format(args.output)) data = FirstData(json_path=Config.DATABASE_JSON) runner = FirstRunner(name=args.runner_name) target_time = FirstTime.from_string(string=args.target_time) if args.ref_race_type is not None: target_time = data.equivalent_time( time_from=target_time, race_index_from=data.race_type_index_by_name( name=args.ref_race_type), race_index_to=data.race_type_index_by_name(name=args.race_type)) if args.race_name is not None: race_name = args.race_name else: race_name = args.race_type race_date = datetime.datetime.strptime(args.race_date, '%m/%d/%Y').date() race = FirstRace(race_type=data.get_race_type_by_name(name=args.race_type), name=race_name, race_date=race_date, target_time=target_time) ws = get_keyrun_days(user_string=args.keyrun_days) plan = FirstPlan(name=args.race_name, weekly_schedule=ws, race=race, runner=runner) plan.generate_workouts(data=data) base_file_name = str(race_date) + race_name if 'text' in args.output: file_name = '{}/{}.{}'.format(Config.DOWNLOADS_DIR, base_file_name, 'txt') target = open(file_name, 'w') target.write(plan.details(level=3)) target.close() if 'tcx' in args.output: file_name = '{}/{}.{}'.format(Config.DOWNLOADS_DIR, base_file_name, 'tcx') target = open(file_name, 'w') target.write(plan.tcx()) target.close() if 'json' in args.output: file_name = '{}/{}.{}'.format(Config.DOWNLOADS_DIR, base_file_name, 'json') target = open(file_name, 'w') target.write(json.dumps(plan.to_json(args.length_unit))) target.close() if 'html' in args.output: file_name = '{}/{}.{}'.format(Config.DOWNLOADS_DIR, base_file_name, 'html') target = open(file_name, 'w') target.write(plan.to_html(args.length_unit)) target.close()
def test_steps_new(self): FirstStepBase.reset_global_id() t_warmup = FirstTime.from_string('0:15:00') p_warmup = FirstPace.from_string('0:10:00 min per mile') s_warmup = FirstStepBody(name='Warm up', pace=p_warmup, time=t_warmup) s_intervals = FirstStepRepeat(name='Intervals', repeat=8) d_interval = FirstDistance.from_string('400 m') p_fast = FirstPace.from_string('0:08:00 min per mile') s_fast = FirstStepBody(name='Fast', pace=p_fast, distance=d_interval) s_slow = FirstStepBody(name='Rest', pace=p_warmup, distance=d_interval) s_intervals.add_step(s_fast) s_intervals.add_step(s_slow) t_cooldown = FirstTime.from_string('0:10:00') s_cooldown = FirstStepBody(name='Cool down', pace=p_warmup, time=t_cooldown) try: # positive wo = FirstWorkout(name='Week 1 Key-run 1', workout_date=date(2017, 6, 24)) wo.add_step(s_warmup) wo.add_step(s_intervals) wo.add_step(s_cooldown) cmp_string = ('Week 1 Key-run 1\n' + '2017-06-24\n' + 'scheduled\n' + '\tStep: "Warm up" id = 0\n' + 'type - body pace - 0:10:00 min per mile\n' + 'Time - 0:15:00\n' + '\tStep: "Intervals" id = 1\n' + 'type - repeat repeat - 8\n' + '\tStep: "Cool down" id = 4\n' + 'type - body pace - 0:10:00 min per mile\n' + 'Time - 0:10:00\n') self.assertEqual(cmp_string, str(wo)) cmp_string = '"Week 1 Key-run 1"\n' +\ ' Sat 2017-06-24\n' +\ ' scheduled\n' +\ ' Step: "Warm up"\n' +\ ' 0:15:00 at 0:10:00 min per mile\n' +\ ' Step: "Intervals"\n' +\ ' Step: "Fast"\n' +\ ' 400.0 m at 0:08:00 min per mile\n' +\ ' Step: "Rest"\n' +\ ' 400.0 m at 0:10:00 min per mile\n' +\ ' Step: "Cool down"\n' +\ ' 0:10:00 at 0:10:00 min per mile\n' +\ ' Totals: distance = 6.48 miles duration = 60.79 minutes\n' self.assertEqual(cmp_string, wo.details(level=2)) total_distance_miles = 15.0 / 10 + 8 * (800 / 1609.344) + 10.0 / 10 self.assertAlmostEqual(total_distance_miles, wo.total(), 5) total_distance_km = total_distance_miles * 1.609344 self.assertAlmostEqual(total_distance_km, wo.total(unit='km'), 5) total_time_minutes = 15.0 + 8 * (400 / 1609.344 * 8 + 400 / 1609.344 * 10) + 10.0 self.assertAlmostEqual(total_time_minutes, wo.total(what='time', unit='minute')) total_time_hours = total_time_minutes / 60.0 self.assertAlmostEqual(total_time_hours, wo.total(what='time', unit='hour')) tcx_string = ( '<Workout Sport="Running">\n' + ' <Name>Week 1 Key-run 1</Name>\n' + ' <Step xsi:type="Step_t">\n' + ' <StepId>0</StepId>\n' + ' <Name>Warm up</Name>\n' + ' <Duration xsi:type="Time_t">\n' + ' <Seconds>900</Seconds>\n' + ' </Duration>\n' + ' <Intensity>Active</Intensity>\n' + ' <Target xsi:type="Speed_t">\n' + ' <SpeedZone xsi:type="CustomSpeedZone_t">\n' + ' <LowInMetersPerSecond>2.6600727</LowInMetersPerSecond>\n' + ' <HighInMetersPerSecond>2.7047798</HighInMetersPerSecond>\n' + ' </SpeedZone>\n' + ' </Target>\n' + ' </Step>\n' + ' <Step xsi:type="Repeat_t">\n' + ' <StepId>1</StepId>\n' + ' <Name>Intervals</Name>\n' + ' <Repetitions>8</Repetitions>\n' + ' <Child xsi:type="Step_t">\n' + ' <StepId>2</StepId>\n' + ' <Name>Fast</Name>\n' + ' <Duration xsi:type="Distance_t">\n' + ' <Meters>400</Meters>\n' + ' </Duration>\n' + ' <Intensity>Active</Intensity>\n' + ' <Target xsi:type="Speed_t">\n' + ' <SpeedZone xsi:type="CustomSpeedZone_t">\n' + ' <LowInMetersPerSecond>3.3182351</LowInMetersPerSecond>\n' + ' <HighInMetersPerSecond>3.3880926</HighInMetersPerSecond>\n' + ' </SpeedZone>\n' + ' </Target>\n' + ' </Child>\n' + ' <Child xsi:type="Step_t">\n' + ' <StepId>3</StepId>\n' + ' <Name>Rest</Name>\n' + ' <Duration xsi:type="Distance_t">\n' + ' <Meters>400</Meters>\n' + ' </Duration>\n' + ' <Intensity>Active</Intensity>\n' + ' <Target xsi:type="Speed_t">\n' + ' <SpeedZone xsi:type="CustomSpeedZone_t">\n' + ' <LowInMetersPerSecond>2.6600727</LowInMetersPerSecond>\n' + ' <HighInMetersPerSecond>2.7047798</HighInMetersPerSecond>\n' + ' </SpeedZone>\n' + ' </Target>\n' + ' </Child>\n' + ' </Step>\n' + ' <Step xsi:type="Step_t">\n' + ' <StepId>4</StepId>\n' + ' <Name>Cool down</Name>\n' + ' <Duration xsi:type="Time_t">\n' + ' <Seconds>600</Seconds>\n' + ' </Duration>\n' + ' <Intensity>Active</Intensity>\n' + ' <Target xsi:type="Speed_t">\n' + ' <SpeedZone xsi:type="CustomSpeedZone_t">\n' + ' <LowInMetersPerSecond>2.6600727</LowInMetersPerSecond>\n' + ' <HighInMetersPerSecond>2.7047798</HighInMetersPerSecond>\n' + ' </SpeedZone>\n' + ' </Target>\n' + ' </Step>\n') tcx_string_end = (' <ScheduledOn>2017-06-24</ScheduledOn>\n' + '</Workout>\n') cmp_string = tcx_string + tcx_string_end self.assertEqual(cmp_string, wo.tcx()) wo.add_step(step=s_warmup) cmp_string = ('Week 1 Key-run 1\n' + '2017-06-24\n' + 'scheduled\n' + '\tStep: "Warm up" id = 0\n' + 'type - body pace - 0:10:00 min per mile\n' + 'Time - 0:15:00\n' + '\tStep: "Intervals" id = 1\n' + 'type - repeat repeat - 8\n' + '\tStep: "Cool down" id = 4\n' + 'type - body pace - 0:10:00 min per mile\n' + 'Time - 0:10:00\n' + '\tStep: "Warm up" id = 0\n' + 'type - body pace - 0:10:00 min per mile\n' + 'Time - 0:15:00\n') self.assertEqual(cmp_string, str(wo)) except ValueError as vex: self.fail(str(vex)) except TypeError as tex: self.fail(str(tex)) try: # wrong type dummy = FirstWorkout(name='Week 1 Key-run 1', workout_date=123) self.fail('Should not get here with a wrong type for date') except TypeError as ex: self.assertEqual('FirstWorkout.__init__ - date must be a datetime', str(ex)) wo1 = FirstWorkout(name='Week 1 Key-run 1', workout_date=date(2017, 4, 1)) try: # change status wo1.set_status('skipped') self.assertEqual( 'Week 1 Key-run 1\n2017-04-01\nskipped\n\tEmpty workout\n', str(wo1)) except ValueError as ex: self.fail(str(ex)) try: # bad status wo1.set_status('lulu') self.fail('Should not get here with bad status') except ValueError as ex: self.assertEqual( "FirstWorkout.set_status - Status not in ['scheduled', 'done', 'skipped']", str(ex))
def test_equivalent_time(self): try: # good path data = FirstData(json_path=Config.DATABASE_JSON) self.assertEqual(4, len(data.race_types)) self.assertEqual(1, data.race_type_index_by_name(name='10K')) self.assertEqual(2, data.race_type_index_by_name(name='HalfMarathon')) self.assertEqual(91, len(data.race_times)) self.assertEqual(4, len(data.race_times[0])) from_time = FirstTime.from_string(string='0:20:13') self.assertEqual( '1:34:15', str( data.equivalent_time(time_from=from_time, race_index_from=0, race_index_to=2))) from_time = FirstTime.from_string('1:54:32') self.assertEqual( '4:01:39', str( data.equivalent_time(time_from=from_time, race_index_from=2, race_index_to=3))) except ValueError as vex: self.fail(str(vex)) except IOError as ioex: self.fail(str(ioex)) try: # bad race name _ = data.race_type_index_by_name('lulu') self.fail('Should not get here with a bad race type name') except ValueError as ex: self.assertEqual('Race type lulu not found', str(ex)) try: # time not found high from_time = FirstTime.from_string('4:49:59') _ = data.equivalent_time(time_from=from_time, race_index_from=2, race_index_to=0) self.fail('Should not get here with time not found') except ValueError as ex: self.assertEqual('Time is longer than the highest database time', str(ex)) try: # time not found low from_time = FirstTime.from_string('0:49:59') _ = data.equivalent_time(time_from=from_time, race_index_from=2, race_index_to=0) self.fail('Should not get here with time not found') except ValueError as ex: self.assertEqual('Time is shorter than the lowest database time', str(ex)) try: # index out of range _ = data.equivalent_time(time_from=from_time, race_index_from=-1, race_index_to=2) self.fail('Should not get here with bad index') except ValueError as ex: self.assertEqual('Race index must be between 0 and 3', str(ex)) try: # index out of range _ = data.equivalent_time(time_from=from_time, race_index_from=4, race_index_to=2) self.fail('Should not get here with bad index') except ValueError as ex: self.assertEqual('Race index must be between 0 and 3', str(ex)) try: # index out of range _ = data.equivalent_time(time_from=from_time, race_index_from=1, race_index_to=-2) self.fail('Should not get here with bad index') except ValueError as ex: self.assertEqual('Race index must be between 0 and 3', str(ex)) try: # index out of range _ = data.equivalent_time(time_from=from_time, race_index_from=1, race_index_to=55) self.fail('Should not get here with bad index') except ValueError as ex: self.assertEqual('Race index must be between 0 and 3', str(ex)) try: # bad database file _ = FirstData(json_path='lulu') self.fail('Should not get here with bad file name') except IOError as ioex: self.assertEqual("[Errno 2] No such file or directory: 'lulu'", str(ioex))
def test_body(self): FirstStepBase.reset_global_id() p1 = FirstPace.from_string('0:10:00 min per mile') d1 = FirstDistance.from_string('3 mile') t1 = FirstTime.from_string('0:15:00') try: # distance step s1 = FirstStepBody(name='3 miles @ 10 minutes per mile', pace=p1, distance=d1) cmp_string = 'Step: "3 miles @ 10 minutes per mile" id = 0\n' + \ 'type - body pace - 0:10:00 min per mile\nDistance - 3.0 mile\n' self.assertEqual(cmp_string, str(s1)) self.assertAlmostEquals(3.0, s1.total(unit='mile'), 5) self.assertAlmostEquals(4.828032, s1.total(unit='km'), 5) self.assertAlmostEquals(30.0, s1.total(what='time', unit='minute'), 5) self.assertAlmostEquals(0.5, s1.total(what='time', unit='hour'), 5) tcx_string = ( '<Step xsi:type="Step_t">\n' + ' <StepId>0</StepId>\n' + ' <Name>3 miles @ 10 minutes per mile</Name>\n' + ' <Duration xsi:type="Distance_t">\n' + ' <Meters>4828</Meters>\n' + ' </Duration>\n' + ' <Intensity>Active</Intensity>\n' + ' <Target xsi:type="Speed_t">\n' + ' <SpeedZone xsi:type="CustomSpeedZone_t">\n' + ' <LowInMetersPerSecond>2.6600727</LowInMetersPerSecond>\n' + ' <HighInMetersPerSecond>2.7047798</HighInMetersPerSecond>\n' + ' </SpeedZone>\n' + ' </Target>\n' + '</Step>\n') self.assertEqual(tcx_string, s1.tcx()) # no indent tcx_string = ( ' <Step xsi:type="Step_t">\n' + ' <StepId>0</StepId>\n' + ' <Name>3 miles @ 10 minutes per mile</Name>\n' + ' <Duration xsi:type="Distance_t">\n' + ' <Meters>4828</Meters>\n' + ' </Duration>\n' + ' <Intensity>Active</Intensity>\n' + ' <Target xsi:type="Speed_t">\n' + ' <SpeedZone xsi:type="CustomSpeedZone_t">\n' + ' <LowInMetersPerSecond>2.6688955</LowInMetersPerSecond>\n' + ' <HighInMetersPerSecond>2.6957186</HighInMetersPerSecond>\n' + ' </SpeedZone>\n' + ' </Target>\n' + ' </Step>\n') self.assertEqual(tcx_string, s1.tcx(indent=' ', delta_seconds=3)) # with indent except TypeError as tex: self.fail(str(tex)) except ValueError as vex: self.fail(str(vex)) try: # time step s1 = FirstStepBody(name='15 minutes @ 10 minutes per mile', pace=p1, time=t1) cmp_string = 'Step: "15 minutes @ 10 minutes per mile" id = 1\n' + \ 'type - body pace - 0:10:00 min per mile\nTime - 0:15:00\n' self.assertEqual(cmp_string, str(s1)) self.assertAlmostEquals(15.0, s1.total(what='time', unit='minute'), 5) self.assertAlmostEquals(7920.0, s1.total(unit='ft')) tcx_string = ( '<Step xsi:type="Step_t">\n' + ' <StepId>1</StepId>\n' + ' <Name>15 minutes @ 10 minutes per mile</Name>\n' + ' <Duration xsi:type="Time_t">\n' + ' <Seconds>900</Seconds>\n' + ' </Duration>\n' + ' <Intensity>Active</Intensity>\n' + ' <Target xsi:type="Speed_t">\n' + ' <SpeedZone xsi:type="CustomSpeedZone_t">\n' + ' <LowInMetersPerSecond>2.6600727</LowInMetersPerSecond>\n' + ' <HighInMetersPerSecond>2.7047798</HighInMetersPerSecond>\n' + ' </SpeedZone>\n' + ' </Target>\n' + '</Step>\n') self.assertEqual(tcx_string, s1.tcx()) except TypeError as tex: self.fail(str(tex)) except ValueError as vex: self.fail(str(vex)) try: # bad name type dummy = FirstStepBody(name=123, pace=p1, time=t1) self.fail('Should not get here with bad name') except TypeError as ex: self.assertEqual('FirstStepBase.__init__ - name must be a string', str(ex)) try: # bad pace dummy = FirstStepBody(name='dummy', pace='bad pace type', time=t1) self.fail('Should not get here with bad pace') except TypeError as ex: self.assertEqual( 'FirstStepBody.__init__ - pace must be an instance of FirstPace', str(ex)) try: # no distance and no time dummy = FirstStepBody(name='dummy', pace=p1) self.fail('Should not get here with neither distance nor duration') except ValueError as ex: self.assertEqual( 'FirstStepBody.__init__ - Either distance or time must have a value', str(ex)) try: # bad distance dummy = FirstStepBody(name='dummy', pace=p1, distance=123.45) self.fail('Should not get here with bad distance') except TypeError as ex: self.assertEqual( 'FirstStepBody.__init__ - distance must be an instance of FirstDistance', str(ex)) try: # bad time dummy = FirstStepBody(name='dummy', pace=p1, time=987.65) self.fail('Should not get here with bad time') except TypeError as ex: self.assertEqual( 'FirstStepBody.__init__ - time must be an instance of FirstTime', str(ex)) try: # both distance and time dummy = FirstStepBody(name='dummy', pace=p1, distance=d1, time=t1) self.fail('Should not get here with both distance and time') except ValueError as ex: self.assertEqual( 'FirstStepBody.__init__ - cannot set both distance and duration in the same step', str(ex))
def test_steps(self): FirstStepBase.reset_global_id() t_warmup = FirstTime.from_string(string='0:15:00') p_warmup = FirstPace.from_string(str_input='0:10:00 min per mile') s_warmup = FirstStepBody(name='Warm up', pace=p_warmup, time=t_warmup) s_intervals = FirstStepRepeat(name='Intervals', repeat=8) d_interval = FirstDistance.from_string(string='400 m') p_fast = FirstPace.from_string(str_input='0:08:00 min per mile') s_fast = FirstStepBody(name='Fast', pace=p_fast, distance=d_interval) s_slow = FirstStepBody(name='Rest', pace=p_warmup, distance=d_interval) s_intervals.add_step(step=s_fast) s_intervals.add_step(step=s_slow) t_cooldown = FirstTime.from_string(string='0:10:00') s_cooldown = FirstStepBody(name='Cool down', pace=p_warmup, time=t_cooldown) try: # positive wo = FirstWorkout(name='Week 1 Key-run 1', workout_date=date(2017, 6, 24)) wo.add_step(step=s_warmup) wo.add_step(step=s_intervals) wo.add_step(step=s_cooldown) cmp_string = ('Week 1 Key-run 1\n' + '2017-06-24\n' + 'scheduled\n' + '\tStep: "Warm up" id = 0\n' + 'type - body pace - 0:10:00 min per mile\n' + 'Time - 0:15:00\n' + '\tStep: "Intervals" id = 1\n' + 'type - repeat repeat - 8\n' + '\tStep: "Cool down" id = 4\n' + 'type - body pace - 0:10:00 min per mile\n' + 'Time - 0:10:00\n') self.assertEqual(cmp_string, str(wo)) cmp_string = '"Week 1 Key-run 1"\n' + \ ' Sat 2017-06-24\n' + \ ' scheduled\n' + \ ' Step: "Warm up"\n' + \ ' 0:15:00 at 0:10:00 min per mile\n' + \ ' Step: "Intervals"\n' + \ ' Step: "Fast"\n' + \ ' 400.0 m at 0:08:00 min per mile\n' + \ ' Step: "Rest"\n' + \ ' 400.0 m at 0:10:00 min per mile\n' + \ ' Step: "Cool down"\n' + \ ' 0:10:00 at 0:10:00 min per mile\n' + \ ' Totals: distance = 6.48 miles duration = 60.73 minutes\n' self.assertEqual(cmp_string, wo.details(level=2)) total_distance_miles = 15.0 / 10 + 8 * (800 / 1609.344) + 10.0 / 10 self.assertAlmostEqual(total_distance_miles, wo.total(), 5) total_distance_km = total_distance_miles * 1.609344 self.assertAlmostEqual(total_distance_km, wo.total(unit='km'), 5) total_time_minutes = 15.0 + 8 * ( round(400 / 1609.344 * 8 * 60) / 60 + round(400 / 1609.344 * 10 * 60) / 60) + 10.0 self.assertAlmostEqual(total_time_minutes, wo.total(what='time', unit='minute'), 5) total_time_hours = total_time_minutes / 60.0 self.assertAlmostEqual(total_time_hours, wo.total(what='time', unit='hour')) tcx_string = ( '<Workout Sport="Running">\n' + ' <Name>Week 1 Key-run 1</Name>\n' + ' <Step xsi:type="Step_t">\n' + ' <StepId>0</StepId>\n' + ' <Name>Warm up</Name>\n' + ' <Duration xsi:type="Time_t">\n' + ' <Seconds>900</Seconds>\n' + ' </Duration>\n' + ' <Intensity>Active</Intensity>\n' + ' <Target xsi:type="Speed_t">\n' + ' <SpeedZone xsi:type="CustomSpeedZone_t">\n' + ' <LowInMetersPerSecond>2.6600727</LowInMetersPerSecond>\n' + ' <HighInMetersPerSecond>2.7047798</HighInMetersPerSecond>\n' + ' </SpeedZone>\n' + ' </Target>\n' + ' </Step>\n' + ' <Step xsi:type="Repeat_t">\n' + ' <StepId>1</StepId>\n' + ' <Name>Intervals</Name>\n' + ' <Repetitions>8</Repetitions>\n' + ' <Child xsi:type="Step_t">\n' + ' <StepId>2</StepId>\n' + ' <Name>Fast</Name>\n' + ' <Duration xsi:type="Distance_t">\n' + ' <Meters>400</Meters>\n' + ' </Duration>\n' + ' <Intensity>Active</Intensity>\n' + ' <Target xsi:type="Speed_t">\n' + ' <SpeedZone xsi:type="CustomSpeedZone_t">\n' + ' <LowInMetersPerSecond>3.3182351</LowInMetersPerSecond>\n' + ' <HighInMetersPerSecond>3.3880926</HighInMetersPerSecond>\n' + ' </SpeedZone>\n' + ' </Target>\n' + ' </Child>\n' + ' <Child xsi:type="Step_t">\n' + ' <StepId>3</StepId>\n' + ' <Name>Rest</Name>\n' + ' <Duration xsi:type="Distance_t">\n' + ' <Meters>400</Meters>\n' + ' </Duration>\n' + ' <Intensity>Active</Intensity>\n' + ' <Target xsi:type="Speed_t">\n' + ' <SpeedZone xsi:type="CustomSpeedZone_t">\n' + ' <LowInMetersPerSecond>2.6600727</LowInMetersPerSecond>\n' + ' <HighInMetersPerSecond>2.7047798</HighInMetersPerSecond>\n' + ' </SpeedZone>\n' + ' </Target>\n' + ' </Child>\n' + ' </Step>\n' + ' <Step xsi:type="Step_t">\n' + ' <StepId>4</StepId>\n' + ' <Name>Cool down</Name>\n' + ' <Duration xsi:type="Time_t">\n' + ' <Seconds>600</Seconds>\n' + ' </Duration>\n' + ' <Intensity>Active</Intensity>\n' + ' <Target xsi:type="Speed_t">\n' + ' <SpeedZone xsi:type="CustomSpeedZone_t">\n' + ' <LowInMetersPerSecond>2.6600727</LowInMetersPerSecond>\n' + ' <HighInMetersPerSecond>2.7047798</HighInMetersPerSecond>\n' + ' </SpeedZone>\n' + ' </Target>\n' + ' </Step>\n') tcx_string_end = (' <ScheduledOn>2017-06-24</ScheduledOn>\n' + '</Workout>') cmp_string = tcx_string + tcx_string_end self.assertEqual(cmp_string, wo.tcx().indented_str()) steps = [{ 'name': 'Warm up', 'pace': { 'length_unit': 'mile', 'pace': '0:10:00 min per mile', 'time': { 'seconds': 600, 'time': '0:10:00' } }, 'time': { 'seconds': 900, 'time': '0:15:00' } }, { 'name': 'Intervals', 'repeat': 8, 'steps': [{ 'distance': { 'distance': 400.0, 'unit': 'm' }, 'name': 'Fast', 'pace': { 'length_unit': 'mile', 'pace': '0:08:00 min per mile', 'time': { 'seconds': 480, 'time': '0:08:00' } } }, { 'distance': { 'distance': 400.0, 'unit': 'm' }, 'name': 'Rest', 'pace': { 'length_unit': 'mile', 'pace': '0:10:00 min per mile', 'time': { 'seconds': 600, 'time': '0:10:00' } } }] }, { 'name': 'Cool down', 'pace': { 'length_unit': 'mile', 'pace': '0:10:00 min per mile', 'time': { 'seconds': 600, 'time': '0:10:00' } }, 'time': { 'seconds': 600, 'time': '0:10:00' } }] cmp_json = { 'date': '2017-06-24', 'name': 'Week 1 Key-run 1', 'note': None, 'status': 'scheduled', 'steps': steps, 'total_distance': { 'distance': 6.47678, 'unit': 'mile' }, 'total_time': { 'time': 60.73333, 'unit': 'minute' } } FirstUtils.assert_deep_almost_equal(self, cmp_json, wo.to_json(), 5) km_steps = [{ 'name': 'Warm up', 'pace': { 'length_unit': 'km', 'pace': '0:06:13 min per km', 'time': { 'seconds': 373, 'time': '0:06:13' } }, 'time': { 'seconds': 900, 'time': '0:15:00' } }, { 'name': 'Intervals', 'repeat': 8, 'steps': [{ 'distance': { 'distance': 0.4, 'unit': 'km' }, 'name': 'Fast', 'pace': { 'length_unit': 'km', 'pace': '0:04:58 min per km', 'time': { 'seconds': 298, 'time': '0:04:58' } } }, { 'distance': { 'distance': 0.4, 'unit': 'km' }, 'name': 'Rest', 'pace': { 'length_unit': 'km', 'pace': '0:06:13 min per km', 'time': { 'seconds': 373, 'time': '0:06:13' } } }] }, { 'name': 'Cool down', 'pace': { 'length_unit': 'km', 'pace': '0:06:13 min per km', 'time': { 'seconds': 373, 'time': '0:06:13' } }, 'time': { 'seconds': 600, 'time': '0:10:00' } }] cmp_json['steps'] = km_steps cmp_json['total_distance'] = {'distance': 10.42336, 'unit': 'km'} FirstUtils.assert_deep_almost_equal(self, cmp_json, wo.to_json(output_unit='km'), 5) cmp_html = ( '<div style="margin-left: 20px">\n' + ' <h3>Week 1 Key-run 1 - Sat, Jun 24 2017</h3>\n' + ' <div style="margin-left: 20px">\n' + ' <p>\n' + ' Warm up - 0:15:00 at 0:10:00 min per mile\n' + ' </p>\n' + ' </div>\n' + ' <div style="margin-left: 20px">\n' + ' <p>\n' + ' Repeat 8 times:\n' + ' </p>\n' + ' <div style="margin-left: 20px">\n' + ' <p>\n' + ' Fast - 400.000 m at 0:08:00 min per mile\n' + ' </p>\n' + ' </div>\n' + ' <div style="margin-left: 20px">\n' + ' <p>\n' + ' Rest - 400.000 m at 0:10:00 min per mile\n' + ' </p>\n' + ' </div>\n' + ' </div>\n' + ' <div style="margin-left: 20px">\n' + ' <p>\n' + ' Cool down - 0:10:00 at 0:10:00 min per mile\n' + ' </p>\n' + ' </div>\n' + ' <table style="border-spacing: 15px 0">\n' + ' <tbody>\n' + ' <tr>\n' + ' <td>Total Distance:</td>\n' + ' <td><b>6.48 mile</b></td>\n' + ' </tr>\n' + ' <tr>\n' + ' <td>Total Time:</td>\n' + ' <td><b>61 minutes</b></td>\n' + ' </tr>\n' + ' </tbody>\n' + ' </table>\n' + '</div>') self.assertEqual(cmp_html, wo.to_html().indented_str()) cmp_html = ( '<div style="margin-left: 20px">\n' + ' <h3>Week 1 Key-run 1 - Sat, Jun 24 2017</h3>\n' + ' <div style="margin-left: 20px">\n' + ' <p>\n' + ' Warm up - 0:15:00 at 0:06:13 min per km\n' + ' </p>\n' + ' </div>\n' + ' <div style="margin-left: 20px">\n' + ' <p>\n' + ' Repeat 8 times:\n' + ' </p>\n' + ' <div style="margin-left: 20px">\n' + ' <p>\n' + ' Fast - 0.400 km at 0:04:58 min per km\n' + ' </p>\n' + ' </div>\n' + ' <div style="margin-left: 20px">\n' + ' <p>\n' + ' Rest - 0.400 km at 0:06:13 min per km\n' + ' </p>\n' + ' </div>\n' + ' </div>\n' + ' <div style="margin-left: 20px">\n' + ' <p>\n' + ' Cool down - 0:10:00 at 0:06:13 min per km\n' + ' </p>\n' + ' </div>\n' + ' <table style="border-spacing: 15px 0">\n' + ' <tbody>\n' + ' <tr>\n' + ' <td>Total Distance:</td>\n' + ' <td><b>10.42 km</b></td>\n' + ' </tr>\n' + ' <tr>\n' + ' <td>Total Time:</td>\n' + ' <td><b>61 minutes</b></td>\n' + ' </tr>\n' + ' </tbody>\n' + ' </table>\n' + '</div>') self.assertEqual(cmp_html, wo.to_html(output_unit='km').indented_str()) wo.add_step(step=s_warmup) cmp_string = ('Week 1 Key-run 1\n' + '2017-06-24\n' + 'scheduled\n' + '\tStep: "Warm up" id = 0\n' + 'type - body pace - 0:10:00 min per mile\n' + 'Time - 0:15:00\n' + '\tStep: "Intervals" id = 1\n' + 'type - repeat repeat - 8\n' + '\tStep: "Cool down" id = 4\n' + 'type - body pace - 0:10:00 min per mile\n' + 'Time - 0:10:00\n' + '\tStep: "Warm up" id = 0\n' + 'type - body pace - 0:10:00 min per mile\n' + 'Time - 0:15:00\n') self.assertEqual(cmp_string, str(wo)) steps.append({ 'name': 'Warm up', 'pace': { 'length_unit': 'mile', 'pace': '0:10:00 min per mile', 'time': { 'seconds': 600, 'time': '0:10:00' } }, 'time': { 'seconds': 900, 'time': '0:15:00' } }) cmp_json['steps'] = steps cmp_json['total_distance'] = {'distance': 7.97678, 'unit': 'mile'} cmp_json['total_time'] = {'time': 75.73333, 'unit': 'minute'} FirstUtils.assert_deep_almost_equal(self, cmp_json, wo.to_json(), 5) km_steps.append({ 'name': 'Warm up', 'pace': { 'length_unit': 'km', 'pace': '0:06:13 min per km', 'time': { 'seconds': 373, 'time': '0:06:13' } }, 'time': { 'seconds': 900, 'time': '0:15:00' } }) cmp_json['steps'] = km_steps cmp_json['total_distance'] = {'distance': 12.83738, 'unit': 'km'} FirstUtils.assert_deep_almost_equal(self, cmp_json, wo.to_json(output_unit='km'), 5) except ValueError as vex: self.fail(str(vex)) except TypeError as tex: self.fail(str(tex)) wo1 = FirstWorkout(name='Week 1 Key-run 1', workout_date=date(2017, 4, 1)) try: # change status wo1.set_status('skipped') self.assertEqual( 'Week 1 Key-run 1\n2017-04-01\nskipped\n\tEmpty workout\n', str(wo1)) except ValueError as ex: self.fail(str(ex)) try: # bad status wo1.set_status('lulu') self.fail('Should not get here with bad status') except ValueError as ex: self.assertEqual("Status not in ['scheduled', 'done', 'skipped']", str(ex))
def test_race(self): rt1 = FirstRaceType(name='5K', distance=FirstDistance.from_string('5.0 km')) rd1 = date(year=2017, month=7, day=29) tt1 = FirstTime.from_string(string='0:25:30') tt2 = FirstTime.from_string(string='0:24:34') try: # positive race = FirstRace(race_type=rt1, race_date=rd1, name='Martial Cottle Park 5K', target_time=tt1) cmp_string = ('Martial Cottle Park 5K of type ' + str(rt1) + '\n' + 'On ' + str(rd1) + '\n' + 'Target time - ' + str(tt1) + '\n' + 'Status - scheduled\n') self.assertEqual(cmp_string, str(race)) cmp_json = { 'Name': 'Martial Cottle Park 5K', 'race_date': '2017-07-29', 'race_type': { 'distance': { 'distance': 5.0, 'unit': 'km' }, 'name': '5K' }, 'status': 'scheduled', 'target_time': { 'seconds': 1530, 'time': '0:25:30' } } self.assertEqual(cmp_json, race.to_json()) cmp_json = { 'Name': 'Martial Cottle Park 5K', 'race_date': '2017-07-29', 'race_type': { 'distance': { 'distance': 3.10686, 'unit': 'mile' }, 'name': '5K' }, 'status': 'scheduled', 'target_time': { 'seconds': 1530, 'time': '0:25:30' } } FirstUtils.assert_deep_almost_equal( self, cmp_json, race.to_json(output_unit='mile'), 5) cmp_html = ( '<div>\n' + ' <h2>Race:</h2>\n' + ' <table style="border-spacing: 15px 0">\n' + ' <tbody>\n' + ' <tr>\n' + ' <td>Name:</td>\n' + ' <td><b>Martial Cottle Park 5K</b></td>\n' + ' </tr>\n' + ' <tr>\n' + ' <td>Type:</td>\n' + ' <td><b>5K</b></td>\n' + ' </tr>\n' + ' <tr>\n' + ' <td>Distance:</td>\n' + ' <td><b>5.000 km</b></td>\n' + ' </tr>\n' + ' <tr>\n' + ' <td>Date:</td>\n' + ' <td><b>2017-07-29</b></td>\n' + ' </tr>\n' + ' <tr>\n' + ' <td>Target time:</td>\n' + ' <td><b>0:25:30</b></td>\n' + ' </tr>\n' + ' <tr>\n' + ' <td>Status:</td>\n' + ' <td><b>scheduled</b></td>\n' + ' </tr>\n' + ' </tbody>\n' + ' </table>\n' + '</div>') self.assertEqual(cmp_html, race.to_html().indented_str()) cmp_html = ( '<div>\n' + ' <h2>Race:</h2>\n' + ' <table style="border-spacing: 15px 0">\n' + ' <tbody>\n' + ' <tr>\n' + ' <td>Name:</td>\n' + ' <td><b>Martial Cottle Park 5K</b></td>\n' + ' </tr>\n' + ' <tr>\n' + ' <td>Type:</td>\n' + ' <td><b>5K</b></td>\n' + ' </tr>\n' + ' <tr>\n' + ' <td>Distance:</td>\n' + ' <td><b>3.107 mile</b></td>\n' + ' </tr>\n' + ' <tr>\n' + ' <td>Date:</td>\n' + ' <td><b>2017-07-29</b></td>\n' + ' </tr>\n' + ' <tr>\n' + ' <td>Target time:</td>\n' + ' <td><b>0:25:30</b></td>\n' + ' </tr>\n' + ' <tr>\n' + ' <td>Status:</td>\n' + ' <td><b>scheduled</b></td>\n' + ' </tr>\n' + ' </tbody>\n' + ' </table>\n' + '</div>') self.assertEqual(cmp_html, race.to_html(output_unit='mile').indented_str()) except TypeError as tex: self.fail(str(tex)) except ValueError as vex: self.fail(str(vex)) try: # add actual time race.set_status(status='done') race.set_actual_time(a_time=tt2) cmp_string = ('Martial Cottle Park 5K of type ' + str(rt1) + '\n' + 'On ' + str(rd1) + '\n' + 'Target time - ' + str(tt1) + '\n' + 'Status - done\n' + 'Actual time - ' + str(tt2) + '\n') self.assertEqual(cmp_string, str(race)) cmp_json = { 'Name': 'Martial Cottle Park 5K', 'actual_time': '0:24:34', 'race_date': '2017-07-29', 'race_type': { 'distance': { 'distance': 5.0, 'unit': 'km' }, 'name': '5K' }, 'status': 'done', 'target_time': { 'seconds': 1530, 'time': '0:25:30' } } self.assertEqual(cmp_json, race.to_json()) cmp_json = { 'Name': 'Martial Cottle Park 5K', 'actual_time': '0:24:34', 'race_date': '2017-07-29', 'race_type': { 'distance': { 'distance': 3.10686, 'unit': 'mile' }, 'name': '5K' }, 'status': 'done', 'target_time': { 'seconds': 1530, 'time': '0:25:30' } } FirstUtils.assert_deep_almost_equal( self, cmp_json, race.to_json(output_unit='mile'), 5) except TypeError as tex: self.fail(str(tex)) except ValueError as vex: self.fail(str(vex)) try: # remove target time race.set_target_time() cmp_string = ('Martial Cottle Park 5K of type ' + str(rt1) + '\n' + 'On ' + str(rd1) + '\n' + 'Status - done\n' + 'Actual time - ' + str(tt2) + '\n') self.assertEqual(cmp_string, str(race)) except TypeError as tex: self.fail(str(tex)) except ValueError as vex: self.fail(str(vex)) try: # negative race.set_status('lulu') self.fail('Should not get here with a bad status') except ValueError as ex: self.assertEqual("Status not in ['scheduled', 'done', 'skipped']", str(ex))
def test_add_workout(self): ws1 = [0, 2, 5] rt1 = FirstRaceType(name='Marathon', distance=42.195, unit='km') rd1 = date(year=2017, month=7, day=29) r1 = FirstRace(name='SFM', race_type=rt1, race_date=rd1) rn1 = FirstRunner(name='DBD') p1 = FirstPlan(name='My first marathon training plan', weekly_schedule=ws1, race=r1, runner=rn1) t_warmup = FirstTime.from_string('0:15:00') p_warmup = FirstPace.from_string('0:10:00 min per mile') s_warmup = FirstStepBody(name='Warm up', pace=p_warmup, time=t_warmup) s_repeat = FirstStepRepeat(name='repeat X 8', repeat=8) d_interval = FirstDistance.from_string('400 m') p_fast = FirstPace.from_string('0:08:00 min per mile') s_fast = FirstStepBody(name='Fast', pace=p_fast, distance=d_interval) s_repeat.add_step(s_fast) s_slow = FirstStepBody(name='Rest', pace=p_warmup, distance=d_interval) s_repeat.add_step(s_slow) t_cooldown = FirstTime.from_string('0:10:00') s_cooldown = FirstStepBody(name='Cool down', pace=p_warmup, time=t_cooldown) wo = FirstWorkout(name='Week 1 Key-run 1', workout_date=date(year=2017, month=6, day=24)) wo.add_step(step=s_warmup) wo.add_step(step=s_repeat) wo.add_step(step=s_cooldown) try: # first workout p1.add_workout(workout=wo) cmp_string = ( 'Training Plan:\nName - "My first marathon training plan"\n' + 'Workout days: Mon, Wed, Sat\nRace:\n' + ' Name - "SFM" of type Marathon - 42.195 km\nRunner:\n Name - "DBD"\nWorkouts:\n' + ' "Week 1 Key-run 1"\n Sat 2017-06-24\n scheduled\n' + 'Total 1 workouts\n') self.assertEqual(cmp_string, str(p1)) file_name = expanduser( '~/PycharmProjects/first/database/cmp_plan2.tcx') # to_file = open(file_name, 'w') # to_file.write(p1.tcx()) # to_file.close() from_file = open(file_name) cmp_string = from_file.read() from_file.close() self.assertEqual(cmp_string, p1.tcx()) except TypeError as ex: self.fail(str(ex)) try: # bad workout p1.add_workout(workout='workout') self.fail('Should not get here with bad workout') except TypeError as ex: self.assertEqual( 'FirstPlan.add_workout - workout must be an instance of FirstWorkout', str(ex))