diff --git a/.testr.conf b/.testr.conf new file mode 100644 index 0000000..6d83b3c --- /dev/null +++ b/.testr.conf @@ -0,0 +1,7 @@ +[DEFAULT] +test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \ + OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \ + OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-60} \ + ${PYTHON:-python} -m subunit.run discover -t ./ . $LISTOPT $IDOPTION +test_id_option=--load-list $IDFILE +test_list_option=--list diff --git a/arbiter/meeting.py b/arbiter/meeting.py index 0eab4a6..c51d760 100644 --- a/arbiter/meeting.py +++ b/arbiter/meeting.py @@ -16,6 +16,7 @@ # under the License. import datetime +import hashlib import logging import os @@ -30,22 +31,9 @@ from arbiter import schedule class Meeting: """An OpenStack meeting.""" - def __init__(self, yaml, filename): + def __init__(self): """Initialize meeting from yaml file name 'filename'.""" - - self.filename = filename - - # initialize using yaml - self.project = yaml['project'] - self.chair = yaml['chair'] - self.description = yaml['description'] - self.agenda = yaml['agenda'] # this is a list of list of topics - - # create schedule objects - self.schedules = [] - for sch in yaml['schedule']: - s = schedule.Schedule(sch) - self.schedules.append(s) + pass def write_ical(self, ical_dir): """Write this meeting to disk using the iCal format.""" @@ -56,7 +44,7 @@ class Meeting: cal.add('prodid', '-//OpenStack//Gerrit-Powered Meeting Agendas//EN') cal.add('version', '2.0') - for schedule in self.schedules: + for sch in self.schs: # one Event per iCal file event = icalendar.Event() @@ -64,12 +52,12 @@ class Meeting: # event in an ical file (at least, for it to work with # Google Calendar) - event.add('summary', self.project + ' (' + schedule.irc + ')') + event.add('summary', self.project + ' (' + sch.irc + ')') # add ical description project_descript = "Project: %s" % (self.project) chair_descript = "Chair: %s" % (self.chair) - irc_descript = "IRC: %s" % (schedule.irc) + irc_descript = "IRC: %s" % (sch.irc) agenda_yaml = yaml.dump(self.agenda, default_flow_style=False) agenda_descript = "Agenda:\n%s\n" % (agenda_yaml) descript_descript = "Description: %s" % (self.description) @@ -82,18 +70,18 @@ class Meeting: # get starting date d = datetime.datetime.utcnow() - next_meeting = self._next_weekday(d, const.WEEKDAYS[schedule.day]) + next_meeting = self._next_weekday(d, const.WEEKDAYS[sch.day]) next_meeting_dt = datetime.datetime(next_meeting.year, next_meeting.month, next_meeting.day, - schedule.time.hour, - schedule.time.minute, + sch.time.hour, + sch.time.minute, tzinfo=pytz.utc) event.add('dtstart', next_meeting_dt) # add recurrence rule - event.add('rrule', {'freq': schedule.freq}) + event.add('rrule', {'freq': sch.freq}) # add meeting length # TODO(jotan): determine duration to use for OpenStack meetings @@ -103,7 +91,7 @@ class Meeting: cal.add_component(event) # determine file name from source file - ical_filename = os.path.basename(self.filename).split('.')[0] + '.ics' + ical_filename = os.path.basename(self._filename).split('.')[0] + '.ics' ical_filename = os.path.join(ical_dir, ical_filename) if not os.path.exists(ical_dir): @@ -127,12 +115,12 @@ class Meeting: """ meetings = [] - for schedule in self.schedules: - schedule_time = schedule.time.hour * 100 + schedule.time.minute + for sch in self.schedules: + schedule_time = sch.time.hour * 100 + sch.time.minute meetings.append((self.filename, (schedule_time, - schedule.day, - schedule.irc))) + sch.day, + sch.irc))) return meetings def _next_weekday(self, ref_date, weekday): @@ -148,3 +136,59 @@ class Meeting: if days_ahead <= 0: # target day already happened this week days_ahead += 7 return ref_date + datetime.timedelta(days_ahead) + + +def load_meetings(yaml_source): + """Build YAML object and load meeting data + + :param yaml_source: source data to load, which can be a directory, file, + or stream. + :returns: list of meeting objects + """ + meetings_yaml = [] + # Determine what the yaml_source is + if os.path.isfile(yaml_source): + meetings_yaml.append(yaml_source) + elif os.path.isdir(yaml_source): + # TODO(lbragstad): use os.path.walk? + for f in os.listdir(yaml_source): + # Build the entire file path and append to the list of yaml + # meetings + yaml_file = os.path.join(yaml_source, f) + meetings_yaml.append(yaml_file) + elif isinstance(yaml_source, str): + return [_load_meeting(yaml_source)] + else: + # If we don't have a .yaml file, a directory of .yaml files, or any + # YAML data fail out here. + raise ValueError("YAML source isn't a .yaml file, directory " + "containing .yaml files, or YAML data.") + + meetings = [] + for yaml_file in meetings_yaml: + with open(yaml_file, 'r') as f: + meetings.append(_load_meeting(f)) + + return meetings + + +def _load_meeting(meeting_yaml): + yaml_obj = yaml.safe_load(meeting_yaml) + m = Meeting() + + # Build meeting attributes from yaml + m.agenda = yaml_obj['agenda'] + m.chair = yaml_obj['chair'] + m.description = yaml_obj['description'] + m.project = yaml_obj['project'] + m._filename = (yaml_obj['project'] + '-' + + hashlib.md5(str(yaml_obj).encode('utf-8')).hexdigest()[:8]) + + # TODO(lbragstad): See if there is another way we can do this instead + # of having every Meeting object build a list of Schedule objects. + m.schedules = [] + for sch in yaml_obj['schedule']: + s = schedule.Schedule(sch) + m.schedules.append(s) + + return m diff --git a/arbiter/tests/__init__.py b/arbiter/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/arbiter/tests/test_meeting.py b/arbiter/tests/test_meeting.py new file mode 100644 index 0000000..bea3e66 --- /dev/null +++ b/arbiter/tests/test_meeting.py @@ -0,0 +1,45 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright 2014 OpenStack Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import unittest + +from arbiter import meeting + +_YAML = """ +project: OpenStack Subteam Meeting +schedule: + - time: '1200' + day: Wednesday + irc: openstack-meeting + frequency: weekly +chair: Joe Developer +description: > + Weekly meeting for Subteam project. +agenda: | + * Top bugs this week +""" + + +class MeetingTestCase(unittest.TestCase): + + def test_load_yaml_file(self): + m = meeting.load_meetings(_YAML)[0] + self.assertEqual('OpenStack Subteam Meeting', m.project) + self.assertEqual('Joe Developer', m.chair) + self.assertEqual('Weekly meeting for Subteam project.\n', + m.description) + self.assertEqual('* Top bugs this week\n', m.agenda) diff --git a/arbiter/util.py b/arbiter/util.py index f243be2..7d587ea 100644 --- a/arbiter/util.py +++ b/arbiter/util.py @@ -20,9 +20,8 @@ import os import yaml -import const -from meeting import Meeting - +from arbiter import const +from arbiter import meeting """Utility functions for check and gate jobs.""" @@ -33,26 +32,6 @@ def publish(meeting, ical): pass -def load_meetings(yaml_dir, meeting_list=None): - """Return a list of Meetings initialized from files in yaml_dir.""" - - meetings_yaml = [] - for file_name in os.listdir(yaml_dir): - yaml_file = os.path.join(yaml_dir, file_name) - if not os.path.isfile(yaml_file): - continue - if meeting_list and yaml_file not in meeting_list: - continue - meetings_yaml.append(yaml_file) - - meetings = [Meeting(yaml.load(open(f, 'r')), f) - for f in meetings_yaml] - - logging.info('Loaded %d meetings from YAML' % (len(meetings))) - - return meetings - - def convert_yaml_to_ical(yaml_dir, ical_dir, meeting_list_file=None): """Convert meeting YAML files to the iCal format and place in ical_dir. If meeting_list is specified, only those meetings @@ -60,13 +39,7 @@ def convert_yaml_to_ical(yaml_dir, ical_dir, meeting_list_file=None): converted; otherwise, all meeting in yaml_dir are converted. """ - - meeting_list = None - if meeting_list_file: - meeting_list = open(meeting_list_file).read().splitlines() - - meetings = load_meetings(yaml_dir, - meeting_list) + meetings = meeting.load_meetings(yaml_dir) # convert meetings to a list of ical for m in meetings: @@ -156,12 +129,12 @@ def _read_yaml_files(directory): meetings = [] for file in yaml_files: - meetings.append(Meeting(yaml.load(open(file, 'r')), file)) + meetings.append(meeting.Meeting(yaml.load(open(file, 'r')), file)) logging.info('Loaded %d meetings form YAML' % len(meetings)) schedules = [] - for meeting in meetings: - for schedule in meeting.get_schedule_tuple(): + for m in meetings: + for schedule in m.get_schedule_tuple(): schedules.append(schedule) return schedules diff --git a/test-requirements.txt b/test-requirements.txt index f0dd00b..9a94963 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1 +1,9 @@ hacking>=0.5.6,<0.8 + +coverage>=3.6 +discover +fixtures>=0.3.14 +python-subunit +testrepository>=0.0.17 +testscenarios>=0.4,<0.5 +testtools>=0.9.32 diff --git a/tox.ini b/tox.ini index 8288be3..01dee04 100644 --- a/tox.ini +++ b/tox.ini @@ -3,8 +3,10 @@ minversion = 1.6 envlist = py27,pep8 [testenv] +install_command = pip install -U {opts} {packages} deps = -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt +commands = python setup.py testr --slowest --testr-args='{posargs}' [testenv:pep8] commands = flake8