diff --git a/gui/mozregui/ui/nightlies.ui b/gui/mozregui/ui/nightlies.ui index 874b3d13e..b59b13c3e 100644 --- a/gui/mozregui/ui/nightlies.ui +++ b/gui/mozregui/ui/nightlies.ui @@ -26,13 +26,6 @@ - - - - yyyy-MM-dd - - - @@ -40,13 +33,6 @@ - - - - yyyy-MM-dd - - - @@ -67,10 +53,24 @@ + + + + + + + + + NightlyInputSelection + QWidget +
mozregui.utils
+ 1 +
+
diff --git a/gui/mozregui/utils.py b/gui/mozregui/utils.py index c18b5bae9..222984a71 100644 --- a/gui/mozregui/utils.py +++ b/gui/mozregui/utils.py @@ -1,6 +1,11 @@ from PyQt4.QtCore import QDir from PyQt4.QtGui import QLineEdit, QPushButton, QWidget, QHBoxLayout, \ - QFileDialog, QFileSystemModel, QCompleter + QFileDialog, QFileSystemModel, QCompleter, QComboBox, QDateEdit, \ + QStackedWidget + +from mozregression.releases import date_of_release, releases +from mozregression.dates import parse_date +from mozregression.errors import DateFormatError class FSLineEdit(QLineEdit): @@ -44,3 +49,45 @@ def browse_dialog(self): ) if path: self.line_edit.setPath(path) + + +class NightlyInputSelection(QWidget): + """ + Allow to select a date, a build id or a release number. + """ + def __init__(self, parent=None): + QWidget.__init__(self, parent) + layout = QHBoxLayout(self) + self._create_widgets() + layout.addWidget(self.stacked) + layout.addWidget(self.select_combo) + self.setLayout(layout) + + def _create_widgets(self): + self.stacked = QStackedWidget() + self.datew = QDateEdit() + self.datew.setDisplayFormat("yyyy-MM-dd") + self.stacked.addWidget(self.datew) + self.buildidw = QLineEdit() + self.stacked.addWidget(self.buildidw) + self.releasew = QComboBox() + self.releasew.addItems([str(k) for k in sorted(releases())]) + self.stacked.addWidget(self.releasew) + + self.select_combo = QComboBox() + self.select_combo.addItems(['date', 'buildid', 'release']) + self.select_combo.activated.connect(self.stacked.setCurrentIndex) + + def get_date(self): + currentw = self.stacked.currentWidget() + if currentw == self.datew: + return self.datew.date().toPyDate() + elif currentw == self.buildidw: + buildid = unicode(self.buildidw.text()) + try: + return parse_date(buildid) + except DateFormatError: + raise DateFormatError(buildid, "Not a valid build id: `%s`") + elif currentw == self.releasew: + return parse_date( + date_of_release(str(self.releasew.currentText()))) diff --git a/gui/mozregui/wizard.py b/gui/mozregui/wizard.py index fe0f5256e..b84825e88 100644 --- a/gui/mozregui/wizard.py +++ b/gui/mozregui/wizard.py @@ -1,6 +1,7 @@ import mozinfo +import datetime from PyQt4.QtGui import QWizard, QWizardPage, QStringListModel, QMessageBox -from PyQt4.QtCore import QString, QDate, QDateTime +from PyQt4.QtCore import QString, QDateTime from ui.intro import Ui_Intro from ui.nightlies import Ui_Nightlies @@ -9,17 +10,8 @@ from mozregression.fetch_configs import create_config, REGISTRY from mozregression.launchers import REGISTRY as LAUNCHER_REGISTRY -from mozregression.errors import LauncherNotRunnable - - -def get_all_subclasses(cls): - all_subclasses = [] - - for subclass in cls.__subclasses__(): - all_subclasses.append(subclass) - all_subclasses.extend(get_all_subclasses(subclass)) - - return all_subclasses +from mozregression.errors import LauncherNotRunnable, DateFormatError +from mozregression.dates import to_datetime def resolve_obj_name(obj, name): @@ -144,20 +136,29 @@ class NightliesPage(WizardSelectionRangePage): UI_CLASS = Ui_Nightlies TITLE = "Date range selection" SUBTITLE = ("Select the nightlies date range.") - FIELDS = {"start_date": "start_date", "end_date": "end_date", - "repository": "repository"} + FIELDS = {"repository": "repository"} ID = 1 def __init__(self): WizardPage.__init__(self) now = QDateTime.currentDateTime() - self.ui.start_date.setDateTime(now.addYears(-1)) - self.ui.end_date.setDateTime(now) + self.ui.start_date.datew.setDateTime(now.addYears(-1)) + self.ui.end_date.datew.setDateTime(now) + + def get_start_date(self): + return self.ui.start_date.get_date() + + def get_end_date(self): + return self.ui.end_date.get_date() def validatePage(self): - start_date = self.ui.start_date.date() - end_date = self.ui.end_date.date() - current = QDateTime.currentDateTime().date() + try: + start_date = to_datetime(self.get_start_date()) + end_date = to_datetime(self.get_end_date()) + except DateFormatError as exc: + QMessageBox.critical(self, "Error", unicode(exc)) + return False + current = datetime.datetime.now() if start_date < end_date: if end_date <= current: return True @@ -231,8 +232,6 @@ def __init__(self, parent=None): # associate current text to comboboxes fields instead of current index self.setDefaultProperty("QComboBox", "currentText", "currentIndexChanged") - # store QDate instead of QDateTime - self.setDefaultProperty("QDateEdit", "date", "dateChanged") self.addPage(IntroPage()) self.addPage(NightliesPage()) @@ -241,17 +240,19 @@ def __init__(self, parent=None): def options(self): options = {} - for wizard_class in get_all_subclasses(WizardPage): + for page_id in self.pageIds(): + wizard_class = self.page(page_id).__class__ for fieldname in wizard_class.FIELDS: value = self.field(fieldname).toPyObject() if isinstance(value, QString): value = unicode(value) - elif isinstance(value, QDate): - value = value.toPyDate() options[fieldname] = value fetch_config = self.page(IntroPage.ID).fetch_config if options['bisect_type'] == 'nightlies': kind = "date" + nightlies_page = self.page(NightliesPage.ID) + options['start_date'] = nightlies_page.get_start_date() + options['end_date'] = nightlies_page.get_end_date() fetch_config.set_nightly_repo(options['repository']) else: kind = "changeset" diff --git a/gui/tests/test_utils.py b/gui/tests/test_utils.py new file mode 100644 index 000000000..15ccab111 --- /dev/null +++ b/gui/tests/test_utils.py @@ -0,0 +1,49 @@ +import pytest +import datetime + +from mozregui.utils import NightlyInputSelection +from mozregression.errors import DateFormatError + + +@pytest.fixture +def pref_editor(qtbot): + widget = NightlyInputSelection() + qtbot.addWidget(widget) + widget.show() + qtbot.waitForWindowShown(widget) + return widget + + +def test_date_widget_is_visible_by_default(pref_editor): + assert pref_editor.select_combo.currentText() == 'date' + assert pref_editor.datew.isVisible() + assert not pref_editor.buildidw.isVisible() + assert not pref_editor.releasew.isVisible() + + +def test_switch_to_release_widget(pref_editor, qtbot): + qtbot.keyClicks(pref_editor.select_combo, "release") + assert pref_editor.select_combo.currentText() == 'release' + assert not pref_editor.datew.isVisible() + assert not pref_editor.buildidw.isVisible() + assert pref_editor.releasew.isVisible() + + +@pytest.mark.parametrize("widname,value,expected", [ + ("date", "20000101", datetime.date(2000, 1, 1)), + ("buildid", "20150102101112", + datetime.datetime(2015, 1, 2, 10, 11, 12)), + ("release", "40", datetime.date(2015, 5, 11)), +]) +def test_get_date(pref_editor, qtbot, widname, value, expected): + qtbot.keyClicks(pref_editor.select_combo, widname) + qtbot.keyClicks(pref_editor.stacked.currentWidget(), value) + assert pref_editor.get_date() == expected + + +def test_get_invalid_buildid(pref_editor, qtbot): + qtbot.keyClicks(pref_editor.select_combo, "buildid") + qtbot.keyClicks(pref_editor.stacked.currentWidget(), "12345") + with pytest.raises(DateFormatError) as ctx: + pref_editor.get_date() + assert 'build id' in str(ctx.value) diff --git a/mozregression/dates.py b/mozregression/dates.py index 4bf2f1db3..5027b7acd 100644 --- a/mozregression/dates.py +++ b/mozregression/dates.py @@ -17,7 +17,7 @@ def parse_date(date_string): try: return datetime.datetime.strptime(date_string, "%Y%m%d%H%M%S") except ValueError: - raise DateFormatError(date_string) + raise DateFormatError(date_string, "Not a valid build id: `%s`") regex = re.compile(r'(\d{4})\-(\d{1,2})\-(\d{1,2})') matched = regex.match(date_string) if not matched: diff --git a/mozregression/errors.py b/mozregression/errors.py index 11063db8c..e9b538e1b 100644 --- a/mozregression/errors.py +++ b/mozregression/errors.py @@ -35,10 +35,8 @@ class DateFormatError(MozRegressionError): """ Raised when a date can not be parsed from a string. """ - def __init__(self, date_string): - MozRegressionError.__init__(self, - "Incorrect date format: `%s`" - % date_string) + def __init__(self, date_string, format="Incorrect date format: `%s`"): + MozRegressionError.__init__(self, format % date_string) class DownloadError(MozRegressionError):