def test_team_with_capacity_on_every_day_has_no_empty_days(self): team, member1 = self.team_with_one_member() member1.capacity = [1] * 7 member1.save() start = now() - timedelta(days=7) end = now() self.assert_length(0, team.capacity().days_without_capacity_in_interval(start, end))
def runTest(self): self._tester.login_as(Usernames.admin) # Create the milestone first self._tester.create_milestone('milestone2') # get sprint listing, should be empty page_url = self._tester.url + '/admin/agilo/sprints' tc.go(page_url) tc.url(page_url) tc.code(200) # add new sprint sprint_start = normalize_date(now()) sprint_name = 'Test sprint' tc.formvalue('addsprint', 'name', sprint_name) tc.formvalue('addsprint', 'start', format_datetime(sprint_start, format='iso8601')) tc.formvalue('addsprint', 'duration', '1') tc.formvalue('addsprint', 'milestone', 'milestone2') tc.submit('add') # add redirects to list view, new sprint should be in there tc.find(sprint_name) # go to detail page tc.go("%s/%s" % (page_url, quote(sprint_name))) # see if milestone is set correctly tc.find('<option selected="selected">\s*milestone2') # test setting end date, not duration tc.formvalue('modcomp', 'description', '[http://www.example.com]') tomorrow = sprint_start + timedelta(days=1) tc.formvalue('modcomp', 'end', format_datetime(tomorrow, format='iso8601')) tc.formvalue('modcomp', 'duration', '') tc.submit('save') tc.url(page_url) # duration of the new sprint should be 2 tc.find('"duration">2</td>') # --- test invalid values when adding sprint --- # no values, should redirect to list view tc.formvalue('addsprint', 'name', '') tc.submit('add') tc.url(page_url) # invalid date, should throw an error tc.formvalue('addsprint', 'name', 'Testsprint 2') tc.formvalue('addsprint', 'start', '2008 May 13') tc.formvalue('addsprint', 'duration', '1') tc.submit('add') tc.find('Error: Invalid Date') # no end date or duration tc.go(page_url) tc.formvalue('addsprint', 'name', 'Testsprint 2') yesterday = now() - timedelta(days=3) tc.formvalue('addsprint', 'start', format_datetime(yesterday, format='iso8601')) tc.submit('add') tc.url(page_url)
def test_team_not_working_on_weekends_has_no_capacity_on_weekends(self): team = self._create_team_with_weekends_off() # exactly one week so we're sure that this interval covers two non-working days start = now() - timedelta(days=7) end = now() days_without_capacity = team.capacity().days_without_capacity_in_interval(start, end) self.assert_length(2, days_without_capacity) self.assert_equals(0, days_without_capacity[0].hour) self.assert_equals(0, days_without_capacity[0].minute)
def test_ideal_burndown_doesnt_raise_division_by_Zero_exception(self): commitment_when = now() - timedelta(days=2) capacities = [ (now() - timedelta(days=3), 1.0), (commitment_when, 0.0), (now() - timedelta(days=1), 0.0), (now(), 0.0) ] self.assert_equals([0,0,0], self._ideal_burndown(2, commitment_when=commitment_when,capacities=capacities))
def test_can_compute_points_with_datetimes(self): two_days_ago = now() - timedelta(days=2) one_day_ago = now() - timedelta(days=1) today = now() line = Line.from_two_points(Point(two_days_ago, 1), Point(one_day_ago, 2)) expected = Point(today, 3) actual = line.point_from_x(today) self.assert_equals(expected.x, actual.x) self.assert_almost_equals(expected.y, actual.y, max_delta=0.1)
def _create_remaining_times(self): self._create_sprint_with_team() self._remaining_time(3, self.sprint.start, Key.COMPONENT, 'foo') self._remaining_time(6, self.sprint.start, Key.COMPONENT, 'bar') self._remaining_time(9, self.sprint.start) self._simulate_confirm_commitment(self.sprint.start) self._remaining_time(-2, now(), Key.COMPONENT, 'foo') self._remaining_time(-4, now(), Key.COMPONENT, 'bar') self._remaining_time(-6, now())
def test_ideal_burndown_doesnt_raise_division_by_Zero_exception(self): commitment_when = now() - timedelta(days=2) capacities = [(now() - timedelta(days=3), 1.0), (commitment_when, 0.0), (now() - timedelta(days=1), 0.0), (now(), 0.0)] self.assert_equals([0, 0, 0], self._ideal_burndown( 2, commitment_when=commitment_when, capacities=capacities))
def _get_today_data(self, start, end, tz): if not (start <= now(tz) <= end): return [] # Now is already calculated in the given timezone so we have to get # the midnight in that timezone, shifted to UTC time # TODO: this is being shifted later, does midnight suffice? today_midnight = midnight_with_utc_shift(now(tz)) return [DayMarker(today_midnight)]
def _request_with_valid_sprint_data(self): start = format_date(now()) end = format_date(now() + timedelta(10)) req = self.teh.mock_request() req.args = dict(name=self.sprint_name(), add=True, start=start, end=end, milestone="Test milestone fnord") return req
def test_tickets_from_other_sprint_not_appearing(self): """ Tests that tasks created for other sprints are not appearing in the sprint backlog, see bug #345 (https://dev.agile42.com/ticket/345) """ s = self.teh.create_sprint("Test") sb = self.teh.create_backlog("Sprint Backlog", num_of_items=100, ticket_types=[Type.USER_STORY, Type.TASK], b_type=BacklogType.SPRINT, scope=s.name) self.assert_length(100, sb) # get a ticket from the backlog and check that it is planned for the sprint self.assert_equals(s.name, sb[10][Key.SPRINT]) # Now add an extra ticket task = self.teh.create_ticket(Type.TASK, props={ Key.SPRINT: s.name, Key.REMAINING_TIME: '2' }) self.assert_length(101, sb) self.assert_contains(task, sb) # Now remove the ticket explicitly and check if the sprint field is set # to None self.teh.move_changetime_to_the_past([task]) sb.remove(task) self.assert_not_contains(task, sb) # reload task and backlog, the remove should have saved the task task = self.teh.load_ticket(task) self.assert_not_contains(task, sb) self.assert_equals('', task[Key.SPRINT]) # Now move the ticket to another sprint s2 = self.teh.create_sprint("Another Sprint") task[Key.SPRINT] = s2.name task.save_changes('tester', 'Moved to sprint %s' % s2.name, when=now() + timedelta(seconds=1)) self.assert_equals(s2.name, task[Key.SPRINT]) # Now should not be in the backlog anymore self.assert_not_contains(task, sb) # Now change sprint again, twice task[Key.SPRINT] = s.name task.save_changes('tester', 'Moved to sprint %s' % s.name, when=now() + timedelta(seconds=2)) self.assert_contains(task, sb) # again task[Key.SPRINT] = s2.name task.save_changes('tester', 'Moved to sprint %s' % s2.name, when=now() + timedelta(seconds=3)) self.assert_equals(s2.name, task[Key.SPRINT]) # Now should not be in the backlog anymore self.assert_not_contains(task, sb)
def setUp(self): self.super() self.teh.disable_sprint_date_normalization() self.sprint = self.teh.create_sprint(name='Sprint 1', start=now() - timedelta(days=3), end=now() + timedelta(days=3)) # already enteres a remaining time! self.task = self.teh.create_task(remaining_time=5, sprint=self.sprint.name) self.assert_true(self.sprint.is_currently_running)
def testUTCDatetimeValidator(self): """Tests the UTCDatetimeValidator""" val = validator.UTCDatetimeValidator(None) utc_now = now(tz=utc) self.assert_equals(utc_now, val.validate(utc_now)) # No UTC datetime self.assert_raises(validator.ValidationError, val.validate, now()) # No datetime self.assert_raises(validator.ValidationError, val.validate, 0) # should allow None, it is not Mandatory self.assert_none(val.validate(None))
def _ideal_burndown(self, commitment, commitment_when=None, capacities=None): commitment_when = commitment_when or now() - timedelta(days=3) if capacities is None: capacities = [ (now() - timedelta(days=3), 6.0), (now() - timedelta(days=2), 4.0), (now() - timedelta(days=1), 2.0), (now(), 0) ] first_burndown = ValueObject(when=commitment_when, remaining_time=commitment) ideal_burndown = calculate_ideal_burndown(capacities, first_burndown, self.sprint) return [remaining for (when, remaining) in ideal_burndown]
def _ideal_burndown(self, commitment, commitment_when=None, capacities=None): commitment_when = commitment_when or now() - timedelta(days=3) if capacities is None: capacities = [(now() - timedelta(days=3), 6.0), (now() - timedelta(days=2), 4.0), (now() - timedelta(days=1), 2.0), (now(), 0)] first_burndown = ValueObject(when=commitment_when, remaining_time=commitment) ideal_burndown = calculate_ideal_burndown(capacities, first_burndown, self.sprint) return [remaining for (when, remaining) in ideal_burndown]
def test_easy_generation_of_burndown_entries(self): actual = BurndownDataChange(self.env).update_values( type='fnord', scope=self.sprint.name, when=now(), delta=3) expected = self.create_change(type='fnord', scope=self.sprint.name, when=now()) expected.set_delta(3) self.assert_equals(expected.type, actual.type) self.assert_equals(expected.scope, actual.scope) self.assert_almost_equals(expected.when, actual.when, max_delta=timedelta(seconds=2)) self.assert_equals(expected.delta(), actual.delta()) self.assert_equals(expected.markers(), actual.markers())
def test_tickets_from_other_sprint_not_appearing(self): """ Tests that tasks created for other sprints are not appearing in the sprint backlog, see bug #345 (https://dev.agile42.com/ticket/345) """ s = self.teh.create_sprint("Test") sb = self.teh.create_backlog("Sprint Backlog", num_of_items=100, ticket_types=[Type.USER_STORY, Type.TASK], b_type=BacklogType.SPRINT, scope=s.name) self.assert_length(100, sb) # get a ticket from the backlog and check that it is planned for the sprint self.assert_equals(s.name, sb[10][Key.SPRINT]) # Now add an extra ticket task = self.teh.create_ticket(Type.TASK, props={Key.SPRINT: s.name, Key.REMAINING_TIME: '2'}) self.assert_length(101, sb) self.assert_contains(task, sb) # Now remove the ticket explicitly and check if the sprint field is set # to None self.teh.move_changetime_to_the_past([task]) sb.remove(task) self.assert_not_contains(task, sb) # reload task and backlog, the remove should have saved the task task = self.teh.load_ticket(task) self.assert_not_contains(task, sb) self.assert_equals('', task[Key.SPRINT]) # Now move the ticket to another sprint s2 = self.teh.create_sprint("Another Sprint") task[Key.SPRINT] = s2.name task.save_changes('tester', 'Moved to sprint %s' % s2.name, when=now() + timedelta(seconds=1)) self.assert_equals(s2.name, task[Key.SPRINT]) # Now should not be in the backlog anymore self.assert_not_contains(task, sb) # Now change sprint again, twice task[Key.SPRINT] = s.name task.save_changes('tester', 'Moved to sprint %s' % s.name, when=now() + timedelta(seconds=2)) self.assert_contains(task, sb) # again task[Key.SPRINT] = s2.name task.save_changes('tester', 'Moved to sprint %s' % s2.name, when=now() + timedelta(seconds=3)) self.assert_equals(s2.name, task[Key.SPRINT]) # Now should not be in the backlog anymore self.assert_not_contains(task, sb)
def runTest(self): team_name = 'team_for_capacity_saving' member_name = 'Team member_name' sprint_name = 'capacity_saving_sprint' self._tester.login_as(Usernames.admin) self._tester.create_new_team(team_name) self._tester.add_member_to_team(team_name, member_name) sprint_start = now() self._tester.create_sprint_via_admin(sprint_name, start=sprint_start, team=team_name) # having tasks with remaining time which were not assigned to a specific # user triggered another bug on the team page. attributes = dict(sprint=sprint_name, remaining_time='12') self._tester.create_new_agilo_task('Not assigned Task', **attributes) self._tester.login_as(Usernames.scrum_master) self._tester.go_to_team_page(team_name, sprint_name) team_page_url = tc.get_browser().get_url() day_ordinal = (sprint_start + timedelta(days=3)).toordinal() input_name = 'ts_%s_%d' % (member_name, day_ordinal) tc.formvalue('team_capacity_form', input_name, '2') tc.submit('save') tc.code(200) tc.url(team_page_url)
def test_continues_straight_line_if_two_values_are_closer_together_than_the_interval_are_given(self): until = now() first = burndown_entry(until - timedelta(minutes=10), 10) second = burndown_entry(until - timedelta(minutes=5), 5) extrapolation = self.generator.calculate([first, second], until) self.assert_length(2, extrapolation) self.assert_equals([burndown_entry(second.when, 5), burndown_entry(until, 0)], extrapolation)
def test_days_without_capacity_respect_given_timestamp(self): team = self._create_team_with_weekends_off() end = now(utc) start = end - timedelta(days=7) bangkok_tz = get_timezone('GMT +7:00') days_without_capacity = team.capacity(bangkok_tz).days_without_capacity_in_interval(start, end) self.assert_equals(bangkok_tz, days_without_capacity[0].tzinfo)
def setUp(self): self.super() self.task = self.teh.create_ticket(Type.TASK) self.task.last_changed = now() - timedelta(seconds=3) self.req = self.teh.mock_request(username='******', args={'ticket_id': self.task.id}, method='POST')
def pre_process_request(self, req, handler): """ Modifies the data of an HTTP request and substitutes type aliases with ticket type names. Always returns the request handler unchanged. """ # Performance measurement setattr(req, START_TIME, now()) #substitute aliases with ticket types in request arguments for typedef in self.TYPE_KEYS: if req.args.has_key(typedef): typedef_obj = req.args.get(typedef) if isinstance(typedef_obj, list): req.args[typedef] = list() # We need a new list for td in typedef_obj: if self._alias_to_type.has_key(td): req.args[typedef].append(self._alias_to_type[td]) else: req.args[typedef].append(td) elif self._alias_to_type.has_key(req.args[typedef]): # print "!!! Replacing %s with %s" % (req.args[typedef], # self._alias_to_type[req.args[typedef]]) req.args[typedef] = self._alias_to_type[req.args[typedef]] return handler
def testTeamMetricsChartContainsAllMetricsDataForMultipleSeries(self): self.env.compmgr.enabled[MetricsChartGenerator] = True today = now() self._add_metrics(self.sprint, **{Key.VELOCITY: 10}) start_sprint2 = today - timedelta(days=30) sprint2 = self.teh.create_sprint(name='Sprint 2', start=start_sprint2, duration=20, team=self.sprint.team) self._add_metrics(sprint2, **{Key.ESTIMATED_VELOCITY: 7}) start_sprint3 = today - 2 * timedelta(days=30) sprint3 = self.teh.create_sprint(name='Sprint 3', start=start_sprint3, duration=20, team=self.sprint.team) self._add_metrics(sprint3, **{Key.VELOCITY: 5}) self._add_metrics(sprint3, **{Key.ESTIMATED_VELOCITY: 9}) widget = ChartGenerator(self.env).get_chartwidget(ChartType.TEAM_METRICS, team_name=self.sprint.team.name, metric_names=[Key.ESTIMATED_VELOCITY, Key.VELOCITY]) self.assert_equals(['Sprint 3', 'Sprint 2', self.sprint.name], widget.data['sprint_names']) metrics = widget.data['metrics'] self.assert_equals(2, len(metrics)) velocity_label, velocity_data = metrics[0] self.assert_equals(get_label(Key.ESTIMATED_VELOCITY), velocity_label) self.assert_equals([(0, 9), (1, 7)], velocity_data) velocity_label, velocity_data = metrics[1] self.assert_equals(get_label(Key.VELOCITY), velocity_label) self.assert_equals([(0, 5), (2, 10)], velocity_data)
def test_can_ignore_tasks_without_sprint(self): self.task = self.teh.create_task() self.inject_remaining_time(now(), 42) self.perform_upgrade(db6.do_upgrade) rows = self.burndown_changes() self.assert_length(0, rows)
def testSprintWithTimezoneDifference(self): """Tests the sprint creation and manipulation with Timezone differences""" # Create a Sprint from Berlin with Daylight risk in Summer berlin_tz = get_timezone('GMT +2:00') # 'Europe/Berlin' start_in_berlin = normalize_date(now(tz=berlin_tz)) cmd_create = SprintController.CreateSprintCommand(self.env, name='TimezoneSprint', milestone='MyRelease', team=self.team.name, start=start_in_berlin, duration=15) sprint = self.controller.process_command(cmd_create) # now reload the sprint and check if the date is still valid # and has been correctly saved... but we will read from San # Francisco sf_tz = get_timezone('GMT -7:00') # 'US/Pacific' # obvious, but you never know what pytz timezone does with the # daylight saving. self.assert_equals(start_in_berlin, start_in_berlin.astimezone(sf_tz)) # check that the sprint.start is in UTC timezone self.assert_equals(timedelta(0), sprint.start.utcoffset()) self.assert_equals(start_in_berlin, sprint.start.astimezone(berlin_tz)) # now we read it as UTC and we create a SF timezone datetime start_in_sf = sprint.start.astimezone(sf_tz) # Python should compare the UTC value of the datetimes self.assert_equals(start_in_berlin, start_in_sf)
def test_can_create_sprints_for_milestones_with_slash_in_name(self): self.teh.create_milestone("milestone/fnord") req = self.teh.mock_request(Usernames.product_owner) req.args = dict(add='add', sprint_name='fnord', start=format_date(now()), duration=10, milestone="milestone/fnord") self.assert_raises(RequestDone, self.view.do_post, req)
def testSprintWithTimezoneDifference(self): """Tests the sprint creation and manipulation with Timezone differences""" # Create a Sprint from Berlin with Daylight risk in Summer berlin_tz = get_timezone('GMT +2:00') # 'Europe/Berlin' start_in_berlin = normalize_date(now(tz=berlin_tz)) cmd_create = SprintController.CreateSprintCommand( self.env, name='TimezoneSprint', milestone='MyRelease', team=self.team.name, start=start_in_berlin, duration=15) sprint = self.controller.process_command(cmd_create) # now reload the sprint and check if the date is still valid # and has been correctly saved... but we will read from San # Francisco sf_tz = get_timezone('GMT -7:00') # 'US/Pacific' # obvious, but you never know what pytz timezone does with the # daylight saving. self.assert_equals(start_in_berlin, start_in_berlin.astimezone(sf_tz)) # check that the sprint.start is in UTC timezone self.assert_equals(timedelta(0), sprint.start.utcoffset()) self.assert_equals(start_in_berlin, sprint.start.astimezone(berlin_tz)) # now we read it as UTC and we create a SF timezone datetime start_in_sf = sprint.start.astimezone(sf_tz) # Python should compare the UTC value of the datetimes self.assert_equals(start_in_berlin, start_in_sf)
def setUp(self): self.super() self.start = normalize_date(now()) duration = timedelta(days=20) self.end = normalize_date(self.start + duration) self.manager = SprintModelManager(self.env) self.tmm = TeamMemberModelManager(self.env)
def create_sprint(self, name, start=None, end=None, duration=20, milestone=None, team=None): """Creates a Sprint for the given milestone, if doesn't exists, first it creates a Milestone""" # If the start day is set to today, the sprint will # normalize it to 9:00am of the start day and all the tests # will fail, till 9:00am in the morning... if start is None: # we set hours to 0 so will be normalized to 9am at any # time of the day, when running tests. start = (now(tz=utc) - timedelta(days=3)).replace(hour=0) if milestone is None: milestone = self.create_milestone('Milestone for %s' % name) # It should automatically load the existing Sprint if already there if isinstance(milestone, Milestone): milestone = milestone.name sprint_controller = SprintController(self.env) if start is not None: start = shift_to_utc(start) if end is not None: end = shift_to_utc(end) create_sprint_command = SprintController.CreateSprintCommand(self.env, name=name, start=start, end=end, duration=duration, milestone=milestone) create_sprint_command.native = True sprint = sprint_controller.process_command(create_sprint_command) assert sprint is not None if team is not None: if isinstance(team, basestring): team = self.create_team(name=team) sprint.team = team sprint.save() return sprint
def _get_remaining_time_now(self, env, sprint): from agilo.scrum import SprintController cmd_class = SprintController.GetTotalRemainingTimeCommand cmd_tot_rem_time = cmd_class(env, sprint=sprint, day=now(tz=utc), tickets=self.tickets) commitment = SprintController(env).process_command(cmd_tot_rem_time) return commitment
def _record_value_change(self, sprint, point_change, component=None, fieldname=Key.REMAINING_TIME): if fieldname == Key.STORY_POINTS: change = BurndownDataChange.remaining_points_entry( self.env, point_change, sprint, now()) else: change = BurndownDataChange.remaining_time_entry( self.env, point_change, sprint, now()) if component is not None and AgiloConfig( self.env).is_filtered_burndown_enabled(): change.update_marker(Key.COMPONENT, component) change.save()
def test_can_not_confirm_if_sprint_started_more_than_one_day_ago(self): self.teh.disable_sprint_date_normalization() team = self.teh.create_team('A-Team') two_days_ago = now() - timedelta(days=2) sprint = self.teh.create_sprint('Sprint', start=two_days_ago, team=team) self.assert_false(self.policy_decision(sprint.resource()))
def testStoreRemainingTimeAsTimestamp(self): task = self.teh.create_ticket(Type.TASK) task[Key.REMAINING_TIME] = '5' task.save_changes(None, None) an_hour_before = now() - timedelta(hours=1) half_an_hour_before = now() - timedelta(minutes=30) remaining = RemainingTime(self.env, task) remaining.set_remaining_time(3, day=an_hour_before) self.assert_equals(5, remaining.get_remaining_time()) self.assert_equals(5, remaining.get_remaining_time(now())) self.assert_equals(3, remaining.get_remaining_time(half_an_hour_before)) self.assert_equals(3, remaining.get_remaining_time(an_hour_before)) self.assert_equals(0, remaining.get_remaining_time(yesterday()))
def testSprintIsNotCurrentlyRunningAfterSprintEnd(self): today = now() sprint_start = today - timedelta(days=20) sprint = self.teh.create_sprint("Test", start=sprint_start) sprint.end = today - timedelta(days=10) # Sprint ended ten days before today self.assert_false(sprint.is_currently_running) self.assert_true(sprint.is_closed)
def test_can_extend_series_till_specified_time(self): first = self.change(timedelta(hours=-10), 10) end = now() aggregated = self.aggregate([first], extend_until=end) self.assert_equals([ burndown_entry(first.when, first.delta()), burndown_entry(end, first.delta()) ], aggregated)
def _execute(self, sp_controller, date_converter, as_key): today = now(tz=utc) day = self.day or today actual_burndown = \ self._get_remaining_times_for_interval(sp_controller.env, day, day, self.sprint, sp_controller, append_current_time=True) self._inject_commitment(actual_burndown, self.commitment) return sum(self._transform_to_old_structure(actual_burndown))
def create_change(self, **kwargs): change = BurndownDataChange(self.env) change.type = 'fnord' change.scope = self.sprint.name change.when = now() for key, value in kwargs.items(): setattr(change, key, value) return change
def _create_sprint_with_team_and_team_member(self, sprint_name, team_name): self._tester.login_as(Usernames.admin) self._tester.create_new_team(team_name) self._tester.add_member_to_team(team_name, 'RestrictOwnerTeamMember') self._tester.create_sprint_via_admin(sprint_name, now(), duration=9, team=team_name)
def test_only_one_point_for_today(self): # this is the crazy burndown chart regression test self.assert_true(self.sprint.is_currently_running) bangkok = get_timezone('GMT +7:00') times = self.get_times_from_chart(tz=bangkok) self.assert_smaller_than(times[-2], times[-1]) self.assert_time_equals(now(), times[-1])
def test_confirm_commitment_uses_current_remaining_time(self): self.sprint.start = now() - timedelta(hours=2) self.sprint.save() task_properties = {Key.REMAINING_TIME: '7', Key.SPRINT: self.sprint.name} self.teh.create_ticket(Type.TASK, props=task_properties) commitment = TeamController.confirm_commitment_for_sprint(self.env, self.sprint) self.assert_equals(7, commitment)
def _execute(self, sp_controller, date_converter, as_key): env = sp_controller.env end = min(self.sprint.end, now(tz=utc)) from agilo.scrum.burndown.model import BurndownDataAggregator aggregator = BurndownDataAggregator(env, self.remaining_field) return aggregator.burndown_data_for_sprint(self.sprint, extend_until=end, filter_by_component=self.filter_by_component)
def _create_remaining_times(self, when_to_create_second_remaining_time=None): self._create_sprint_with_team() self._remaining_time(3 + 6 + 9, self.sprint.start) # 18 if when_to_create_second_remaining_time is None: when_to_create_second_remaining_time = now() - timedelta(hours=3) self._remaining_time(-1 + -2 + -3, when_to_create_second_remaining_time) # -6